Commit Diff


commit - 507b56e50b583013fc420b2f728f2506dd6243bd
commit + fb1a36c0a6028fb69d26ed62cafee077a0c345ce
blob - d66373e2c39f38c9784d99526c519fbd3bae5a86
blob + 9bed9c14e5443d7a8d3b464cfde197fd0ad7abf9
--- .gitignore
+++ .gitignore
@@ -1,37 +1,14 @@
-Makefile
-Makefile.in
-aclocal.m4
-autom4te.cache
-compile
-config.h.in
-configure
-depcomp
-install-sh
-missing
-ylwrap
-stamp-h1
-config.guess
-config.h
-config.log
-config.status
-config.sub
-.deps
 *~
+**/obj
+**/tags
+**/*.d
+**/*.o
 
-*.log
-*.trs
+**/parse.c
+**/parse.h
 
-compile_flags.txt
-
-kamid
-kamictl
-kamiftp
-kamirepl
-*.o
-kamid.conf
-parse.c
-np.c
-ninepscript
-
-*.crt
-*.pem
+kamictl/kamictl
+kamid/kamid
+kamiftp/kamiftp
+kamirepl/kamirepl
+ninepscript/ninepscript
blob - 13e41c7900a696a6570e9a462d3bef9d850745a0 (mode 644)
blob + /dev/null
--- 9p.7
+++ /dev/null
@@ -1,393 +0,0 @@
-.\" 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.
-.\"
-.Dd $Mdocdate: July 30 2021 $
-.Dt 9P 7
-.Os
-.Sh NAME
-.Nm 9P
-.Nd Simple Distributed File System
-.Sh DESCRIPTION
-.Nm
-is a protocol that implements a distributed file systems.
-It provides primitives to manage
-.Pq create, read, write and delete
-sets of files remotely.
-These files don't necessarily need to be actually stored on a disk,
-they may be, for example, synthesise on demand from external sources.
-.Pp
-A client transmits requests
-.Pq T-messages
-to a server, which returns replies
-.Pq R-messages
-to the client.
-The combined acts of transmitting a request of a particular type and
-receiving a reply is called a transaction of that type.
-.Pp
-Each message consists of a sequence of bytes mostly grouped in one,
-two or four integer fields transmitted in little-endian order
-.Pq least significant byte first .
-Data items of larger or variable lengths are represented by a two-byte
-field specifying the length followed by the actual data.
-The only exception to this rule are QIDs, thirteen byte long
-objects, that are sent as-is.
-.Pp
-Text strings are represented with a two-byte count and the sequence of
-UNICODE codepoints encoded in UTF-8.
-Text strings in 9p are not NUL-terminated.
-The NUL-terminator is illegal in all text strings and thus excluded
-from paths, user names and so on.
-.Pp
-Fields are hereafter denoted as
-.Bd -literal -offset indent
-type[1] tag[2] fid[4]
-.Ed
-.Pp
-to indicate that type is one byte long, tag two and fid four.
-Strings are denoted as name[s] and are sent on the wire as
-.Bd -literal -offset indent
-length[2] string[length]
-.Ed
-.Pp
-A qid, described later, is a 13-byte value that is sent on the wire as
-.Bd -literal -offset indent
-type[1] version[4] path[8]
-.Ed
-.Sh MESSAGE STRUCTURE
-Every message has a header with the following fields:
-.Bd -literal -offset indent
-len[4] type[1] tag[2]
-.Ed
-.Pp
-where len indicates the overall length of the message, including
-itself; type is one byte indicating the type of the message and the
-tag is a number choosen by the client that indicate uniquely the
-request.
-Then follows an optional body whose structure depends on the type of
-the message.
-.Pp
-The message types are as follows:
-.Pq the header is omitted for brevity
-.Bl -tag -width versionxx
-.It Ic version
-Negotiate the version and maximum message size.
-.Bd -literal
-msize[4] version[s]
-msize[4] version[s]
-.Ed
-.Pp
-The
-.Ic version
-request must be the first message sent, and the client cannot issue
-further requests until receiving the Rversion reply.
-.Cm tag
-should be
-.Dv NOTAG
-.Pq \-1 or 255 .
-The client suggest a
-.Cm msize
-.Pq the maximum size for packets
-and the protocol version used, the server replies with a
-.Cm msize
-smaller or equal to the one proposed by the client.
-The version string must always begin with the two character
-.Dq 9P .
-If the server don't understand the client required version, should
-reply with a Rversion using the version string
-.Dq unknown
-and not use a Rerror.
-.It Ic attach
-Populate the namespace
-.Bd -literal
-fid[4] afid[4] uname[s] aname[s]
-qid[13]
-.Ed
-.Pp
-The
-.Ic attach
-message binds the given
-.Ar fid
-to the root of the file tree identified by
-.Ar aname .
-.Ar uname
-identifies the user and
-.Ar afid
-specifies a fid previously established by an auth message, or the
-special
-.Dv NOFID
-value
-.Pq defined as (u32int)~0
-if the authentication is not required.
-.It Ic clunk
-Close fids.
-.Bd -literal
-fid[4]
-.Aq empty response
-.Ed
-.Pp
-Once a fid has been clunked
-.Pq closed
-it becomes
-.Dq free
-and the same value can be used for subsequential
-.Ic walk
-or
-.Ic attach
-requests.
-.Pp
-The actual file on the disk is not remove unless it was opened with the
-.Dv ORCLOSE
-flag.
-.It Ic error
-Return an error string.
-.Bd -literal
-ename[s]
-.Ed
-.Pp
-The Rerror message is used to return an error string describing the
-failure of a request.
-The
-.Cm tag
-indicates the failed request.
-.Pp
-Note that there isn't a
-.Ic Terror
-request for obvious reason and it's not possible for a server to reply to
-a
-.Ic Tversion
-or
-.Ic Tflush
-using
-.Ic Rerror .
-.It Ic flush
-Abort an ongoing operation.
-.Bd -literal
-oldtag[2]
-.Aq empty response
-.Ed
-.Pp
-Given the asynchronous nature of the protocol, the server may respond to
-the pending request before responding to the
-.Ic Tflush
-and is possible for a client to send multiple
-.Ic Tflush
-for the same operation.
-The client must wait to receive a corresponding
-.Ic Rflush
-before reusing
-.Ar oldtag
-for subsequent messages.
-.Pp
-If a response for
-.Ar oldtag
-is received before the
-.Ic Rflush
-reply, the client must assume that the operation was completed with success
-.Pq fid allocated, files created, ...
-If no response is received before the
-.Ic Rflush
-then the transaction is considered to have been successfully cancelled.
-.Pp
-Note that the tag of this request and the corresponding reply is NOT
-.Ar oldtag
-but a new tag value.
-.It Ic walk
-Traverse a file tree.
-.Bd -literal
-fid[4] newfid[4] nwname[2] nwname*(wname[s])
-nwqid[2] nwqid*(qid[13])
-.Ed
-.Pp
-The
-.Ar nwname
-components are walked in order starting from
-.Ar fid
-.Pq which must point to a directory
-and, if successful,
-.Ar newfid
-is associated to the reached file.
-.Pp
-It is possible for
-.Ar fid
-and
-.Ar newfid
-to be equal, in this case the fid is
-.Dq mutated ,
-otherwise
-.Ar newfid
-must be unused.
-As a special case, a walk of zero components duplicates the fid.
-.Pp
-If the first element cannot be walked for any reason an
-.Ic Rerror
-is returned.
-Otherwise,
-.Ic Rwalk
-is returned with a number of qids equal to the file viside by the walk.
-A client can thus detect a walk when that the replied
-.Ar nwqid
-number is not equal to the
-.Ar nwname
-field in the request.
-Only when walk return successfully
-.Ar newfid
-will be affected.
-.Pp
-A maximum of 16 component can be used per walk request.
-.It Ic open
-Prepare a fid for I/O.
-.Bd -literal
-fid[4] mode[1]
-qid[13] iounit[4]
-.Ed
-.Pp
-.Ar mode
-determines the type of I/O:
-.Bl -tag -width Ds -offset indent -compact
-.It 0 Dv OREAD
-.It 1 Dv OWRITE
-.It 2 Dv ORDWD
-.It 3 Dv OEXEC
-.El
-.Pp
-The returned
-.Ar iounit
-is the optimal blocksize for I/O.
-.It Ic create
-Create a file
-.Bd -literal
-fid[4] name[s] perm[4] mode[1]
-qid[13] iounit[4]
-.Ed
-.Pp
-The call attempts to create a file named
-.Ar name
-in the directory identified by
-.Ar fid
-according to
-.Ar perm
-and then to open it with
-.Ar mode
-into the given
-.Ar fid .
-.Pp
-It is illegal to use an already opened
-.Ar fid
-or to attempt to create the
-.Dq \&.
-or
-.Dq ..
-entries.
-.It Ic read
-Read data at offset from file
-.Bd -literal
-fid[4] offset[8] count[4]
-count[4] data[count]
-.Ed
-.Pp
-.Ar fid
-must have been prepared for I/O with a previous
-.Ic open
-call.
-The returned
-.Ar count
-is zero when reaching end-of-file and may be lesser than what requested.
-.Pp
-Directories are a stream of stat structures, as described in
-.Ic stat ,
-and for them the read request message must have offset equal to zero or
-the value of
-.Ar offset
-in the previous read on the directory plus the number of bytes returned
-in the previous read.
-Thus, is not possible to seek into directories except for rewinding.
-.It Ic write
-Write data at offset
-.Bd -literal
-fid[4] offset[8] count[4] data[count]
-count[4]
-.Ed
-.It Ic stat
-get file status
-.Bd -literal
-fid[4]
-stat[n]
-.Ed
-.Pp
-The stat structure is made by the following fields:
-.Bl -tag -width twelveletters -compact
-.It size[2]
-total byte count of the following data
-.It type[2]
-for kernel use
-.It dev[4]
-for kernel use
-.It qid[13]
-server unique identifier of the file
-.It mode[4]
-permissions and flags
-.It atime[4]
-last access time
-.It mtime[4]
-last modification time
-.It length[8]
-length of file in bytes
-.It name[s]
-file name
-(must be
-.Dq /
-if the file is the root directory of the server)
-.It uid[s]
-owner name
-.It gid[s]
-group name
-.It muid[s]
-name of the user who last modified the file.
-.El
-.It Ic remove
-Remove a file
-.Bd -literal
-fid[4]
-.Aq empty response
-.Ed
-.El
-.\" .Sh 9P2000.L EXTENSIONS
-.\" .Xr kamid 8
-.\" supports also a subset of the
-.\" .Sq 9P2000.L
-.\" dialect.
-.\" The supported messages are
-.\" .Bl -tag -width readdir
-.\" .It Ic readdir
-.\" Read directory entries
-.\" .Bd -literal
-.\" fid[4] offset[8] count[4]
-.\" count[4] data[count]
-.\" .Ed
-.\" .Pp
-.\" Each directory entry is described by a variable-length record:
-.\" .Ql qid[13] offset[8] type[1] name[s] .
-.\" Offset is zero upon the first call.
-.\" If the
-.\" .Ar count
-.\" field in the
-.\" .Ic Rreaddir
-.\" response is not zero then more data is available.
-.\" .Pp
-.\" .Ar count
-.\" is allowed to be zero in the request.
-.\" .El
-.Sh SEE ALSO
-.Xr utf8 7 ,
-.Xr kamid 8
blob - 1fc4c49d81df79abe7260d7022578376090f177a (mode 644)
blob + /dev/null
--- 9pclib.c
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * 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 "compat.h"
-
-#include <endian.h>
-#include <inttypes.h>
-#include <string.h>
-
-#include "9pclib.h"
-#include "kamid.h"
-#include "log.h"
-#include "utils.h"
-
-uint16_t iota_tag;
-
-struct evbuffer *evb;
-
-void
-write_hdr(uint32_t len, uint8_t type, uint16_t tag)
-{
-	len += HEADERSIZE;
-
-	log_debug("enqueuing a packet; len=%"PRIu32" type=%d[%s] tag=%d",
-	    len, type, pp_msg_type(type), tag);
-
-	len = htole32(len);
-	/* type is one byte, no endiannes issues */
-	tag = htole16(tag);
-
-	evbuffer_add(evb, &len, sizeof(len));
-	evbuffer_add(evb, &type, sizeof(type));
-	evbuffer_add(evb, &tag, sizeof(tag));
-}
-
-void
-write_hdr_auto(uint32_t len, uint8_t type)
-{
-	if (++iota_tag == NOTAG)
-		++iota_tag;
-	write_hdr(len, type, iota_tag);
-}
-
-void
-write_str(uint16_t len, const char *str)
-{
-	uint16_t l = len;
-
-	len = htole16(len);
-	evbuffer_add(evb, &len, sizeof(len));
-	evbuffer_add(evb, str, l);
-}
-
-void
-write_str_auto(const char *str)
-{
-	write_str(strlen(str), str);
-}
-
-void
-write_buf(const void *d, uint32_t len)
-{
-	write_32(len);
-	evbuffer_add(evb, d, len);
-}
-
-void
-write_64(uint64_t x)
-{
-	x = htole64(x);
-	evbuffer_add(evb, &x, sizeof(x));
-}
-
-void
-write_32(uint32_t fid)
-{
-	fid = htole32(fid);
-	evbuffer_add(evb, &fid, sizeof(fid));
-}
-
-void
-write_16(uint16_t tag)
-{
-	tag = htole16(tag);
-	evbuffer_add(evb, &tag, sizeof(tag));
-}
-
-void
-write_8(uint8_t x)
-{
-	evbuffer_add(evb, &x, sizeof(x));
-}
-
-
-
-void
-tversion(const char *v, uint32_t msize)
-{
-	uint32_t	len;
-	uint16_t	sl;
-
-	sl = strlen(v);
-
-	/* msize[4] version[s] */
-	len = sizeof(msize) + sizeof(sl) + sl;
-	write_hdr(len, Tversion, NOTAG);
-	write_32(msize);
-	write_str(sl, v);
-}
-
-void
-tattach(uint32_t fid, uint32_t afid, const char *uname, const char *aname)
-{
-	uint32_t	len;
-	uint16_t	ul, al;
-
-	ul = strlen(uname);
-	al = strlen(aname);
-
-	/* fid[4] afid[4] uname[s] aname[s] */
-	len = sizeof(fid) + sizeof(afid) + sizeof(ul) + ul
-	    + sizeof(al) + al;
-	write_hdr_auto(len, Tattach);
-	write_fid(fid);
-	write_fid(afid);
-	write_str(ul, uname);
-	write_str(al, aname);
-}
-
-void
-tclunk(uint32_t fid)
-{
-	uint32_t	len;
-
-	/* fid[4] */
-	len = sizeof(fid);
-	write_hdr_auto(len, Tclunk);
-	write_fid(fid);
-}
-
-void
-tflush(uint16_t oldtag)
-{
-	uint32_t	len;
-
-	/* oldtag[2] */
-	len = sizeof(oldtag);
-	write_hdr_auto(len, Tflush);
-	write_tag(oldtag);
-}
-
-void
-twalk(uint32_t fid, uint32_t newfid, const char **wnames, size_t nwname)
-{
-	size_t		i;
-	uint32_t	len;
-
-	/* fid[4] newfid[4] nwname[2] nwname*(wname[s]) */
-	len = sizeof(fid) + sizeof(newfid) + 2;
-	for (i = 0; i < nwname; ++i)
-		len += 2 + strlen(wnames[i]);
-
-	write_hdr_auto(len, Twalk);
-	write_fid(fid);
-	write_fid(newfid);
-	write_16(nwname);
-	for (i = 0; i < nwname; ++i)
-		write_str_auto(wnames[i]);
-}
-
-void
-topen(uint32_t fid, uint8_t mode)
-{
-	uint32_t	len;
-
-	/* fid[4] mode[1] */
-	len = sizeof(fid) + sizeof(mode);
-	write_hdr_auto(len, Topen);
-	write_fid(fid);
-	write_8(mode);
-}
-
-void
-tcreate(uint32_t fid, const char *name, uint32_t perm, uint8_t mode)
-{
-	uint32_t	len;
-	uint16_t	nl;
-
-	/* fid[4] name[s] perm[4] mode[1] */
-	nl = strlen(name);
-	len = sizeof(fid) + sizeof(nl) + nl + sizeof(perm) + sizeof(mode);
-	write_hdr_auto(len, Tcreate);
-	write_fid(fid);
-	write_str(nl, name);
-	write_32(perm);
-	write_8(mode);
-}
-
-void
-tread(uint32_t fid, uint64_t off, uint32_t count)
-{
-	uint32_t	len;
-
-	/* fid[4] off[8] count[4] */
-	len = sizeof(fid) + sizeof(off) + sizeof(count);
-	write_hdr_auto(len, Tread);
-	write_fid(fid);
-	write_off(off);
-	write_32(count);
-}
-
-void
-twrite(uint32_t fid, uint64_t off, const void *data, uint32_t count)
-{
-	uint32_t	len;
-
-	/* fid[4] off[8] count[4] data[count] */
-	len = sizeof(fid) + sizeof(off) + sizeof(count) + count;
-	write_hdr_auto(len, Twrite);
-	write_fid(fid);
-	write_off(off);
-	write_buf(data, count);
-}
-
-void
-tstat(uint32_t fid)
-{
-	/* fid[4] */
-	write_hdr_auto(sizeof(fid), Tstat);
-	write_fid(fid);
-}
-
-void
-tremove(uint32_t fid)
-{
-	/* fid[4] */
-	write_hdr_auto(sizeof(fid), Tremove);
-	write_fid(fid);
-}
blob - /dev/null
blob + 1b2fbf9ebe7ea86acf1e7d76453c86314a33db66 (mode 644)
--- /dev/null
+++ Makefile
@@ -0,0 +1,30 @@
+SUBDIR = 
+SUBDIR += kamictl
+SUBDIR += kamid
+SUBDIR += kamiftp
+SUBDIR += kamirepl
+SUBDIR += ninepscript
+
+.if make(regress) || make(obj) || make(clean) || make(release)
+SUBDIR += regress
+.endif
+
+.if make(tags) || make(cleandir)
+SUBDIR += lib
+.endif
+
+.include "kamid-version.mk"
+
+release: clean
+	sed -i -e 's/_RELEASE=No/_RELEASE=Yes/' kamid-version.mk
+	${MAKE} dist
+	sed -i -e 's/_RELEASE=Yes/_RELEASE=No/' kamid-version.mk
+
+dist: clean
+	mkdir /tmp/kamid-${KAMID_VERSION}
+	pax -rw * /tmp/kamid-${KAMID_VERSION}
+	find /tmp/kamid-${KAMID_VERSION} -name obj -type d -delete
+	tar -C /tmp -zcf kamid-${KAMID_VERSION}.tar.gz kamid-${KAMID_VERSION}
+	rm -rf /tmp/kamid-${KAMID_VERSION}
+
+.include <bsd.subdir.mk>
blob - 8f3dce09953080d4a0196e351d08a1bc474801f0 (mode 644)
blob + /dev/null
--- 9pclib.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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.
- */
-
-/* 9p client library */
-
-#ifndef NPCLIB_H
-#define NPCLIB_H
-
-#include "compat.h"
-
-#include <stdint.h>
-
-extern uint16_t		 iota_tag;
-extern struct evbuffer	*evb;
-
-void		 write_hdr(uint32_t, uint8_t, uint16_t);
-void		 write_hdr_auto(uint32_t, uint8_t);
-void		 write_str(uint16_t, const char *);
-void		 write_str_auto(const char *);
-void		 write_buf(const void *, uint32_t);
-void		 write_64(uint64_t);
-void		 write_32(uint32_t);
-void		 write_16(uint16_t);
-void		 write_8(uint8_t);
-
-#define write_off write_64
-#define write_fid write_32
-#define write_tag write_16
-
-void		 tversion(const char *, uint32_t);
-void		 tattach(uint32_t, uint32_t, const char *, const char *);
-void		 tclunk(uint32_t);
-void		 tflush(uint16_t);
-void		 twalk(uint32_t, uint32_t, const char **, size_t);
-void		 topen(uint32_t, uint8_t);
-void		 tcreate(uint32_t, const char *, uint32_t, uint8_t);
-void		 tread(uint32_t, uint64_t, uint32_t);
-void		 twrite(uint32_t, uint64_t, const void *, uint32_t);
-void		 tstat(uint32_t);
-void		 tremove(uint32_t);
-
-#endif
blob - /dev/null
blob + 03a3fcfd656b454b409ec8f124c32499814a081b (mode 644)
--- /dev/null
+++ Makefile.inc
@@ -0,0 +1,17 @@
+CPPFLAGS += -DKAMID_VERSION="\"${KAMID_VERSION}\""
+
+.if ${KAMID_RELEASE} == Yes
+PREFIX ?= /usr/local
+BINDIR ?= ${PREFIX}/bin
+MANDIR ?= ${PREFIX}/man/man
+.else
+CFLAGS += -Werror -Wall -Wmissing-prototypes -Wstrict-prototypes
+CFLAGS += -Wwrite-strings -Wno-unused-parameter
+PREFIX ?= ${HOME}
+BINDIR ?= ${PREFIX}/bin
+
+BINOWN ?= ${USER}
+.if !defined(BINGRP)
+BINGRP != id -g -n
+.endif
+.endif
blob - 02f6fb699db91df77c25a037d80d3d05bbf03f3f (mode 644)
blob + /dev/null
--- Makefile.am
+++ /dev/null
@@ -1,90 +0,0 @@
-bin_PROGRAMS =		kamictl kamid kamiftp kamirepl
-check_PROGRAMS =	ninepscript
-
-EXTRA_kamid_SOURCES =	compat/ohash.h	\
-			compat/imsg.h	\
-			compat/queue.h	\
-			compat/tree.h	\
-			compat/vis.c	\
-			compat/vis.h
-
-kamictl_SOURCES =	compat.h	\
-			ctl_parser.c	\
-			ctl_parser.h	\
-			kamictl.c	\
-			kamid.h		\
-			log.c		\
-			log.h		\
-			sandbox.c	\
-			sandbox.h
-
-kamid_SOURCES =		client.c	\
-			client.h	\
-			control.c	\
-			control.h	\
-			compat.h	\
-			kamid.c		\
-			kamid.h		\
-			listener.c	\
-			listener.h	\
-			log.c		\
-			log.h		\
-			parse.y		\
-			sandbox.c	\
-			sandbox.h	\
-			table.c		\
-			table.h		\
-			table_static.c	\
-			utils.c		\
-			utils.h
-
-kamiftp_SOURCES =	9pclib.c	\
-			9pclib.h	\
-			ftp.c		\
-			kamid.h		\
-			log.c		\
-			log.h		\
-			utils.c		\
-			utils.h
-
-kamirepl_SOURCES =	9pclib.c	\
-			9pclib.h	\
-			kamid.h		\
-			kamirepl.c	\
-			log.c		\
-			log.h		\
-			utils.c		\
-			utils.h
-
-ninepscript_SOURCES =	client.c	\
-			client.h	\
-			compat.h	\
-			log.c		\
-			log.h		\
-			np.y		\
-			sandbox.c	\
-			script.c	\
-			script.h	\
-			utils.c		\
-			utils.h
-
-AM_CFLAGS =		@AM_CFLAGS@
-LDADD =			$(LIBOBJS)
-
-kamiftp_LDFLAGS =	@KAMIFTP_LIBS@
-
-BUILT_SOURCES =		compile_flags.txt
-CLEANFILES =		compile_flags.txt
-
-dist_doc_DATA =		README.md LICENSE
-dist_man1_MANS =	kamiftp.1 kamirepl.1
-dist_man5_MANS =	kamid.conf.5
-dist_man7_MANS =	9p.7
-dist_man8_MANS =	kamictl.8 kamid.8
-
-EXTRA_DIST =		regress ninepscript.5 ninepscript.8
-TESTS =			run-tests.sh
-run-tests.sh:		ninepscript
-
-compile_flags.txt:
-	printf "%s\n" ${CFLAGS} > compile_flags.txt
blob - fd41e06c1f4122da4d2d057b858dea606c200931
blob + 5d6c473507d76e97317947a11b206258c7280214
--- TODO
+++ TODO
@@ -34,6 +34,6 @@ Stuff that's still to-do
    One solution may be to not use readdir and roll our own on top
    of getdents(2) and lseek(2), but that may not be portable.
 
- - reply with an Rerror in listener.c:/^client_read when we get an
-   invalid message (too big or too small) before closing the
+ - reply with an Rerror in kamid/listener.c:/^client_read when we get
+   an invalid message (too big or too small) before closing the
    connection.
blob - /dev/null
blob + 9074383dc2bbfc282ccb63be5fc10a3585b6c817 (mode 644)
--- /dev/null
+++ kamictl/Makefile
@@ -0,0 +1,18 @@
+.PATH:${.CURDIR}/../lib
+
+.include "../kamid-version.mk"
+
+PROG=		kamictl
+SRCS=		ctl_parser.c kamictl.c log.c sandbox.c
+MAN=		kamictl.8
+
+CPPFLAGS=	-I${.CURDIR}/../lib -I${.CURDIR}/../kamid/ -I${.CURDIR}
+
+LDADD+= -lutil
+DPADD+= ${LIBUTIL}
+
+.if ${KAMID_RELEASE} != Yes
+NOMAN= Yes
+.endif
+
+.include <bsd.prog.mk>
blob - /dev/null
blob + b0515084f057d8663be62be76614f58434f45c2c (mode 644)
--- /dev/null
+++ kamictl/ctl_parser.c
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2004 Esben Norby <norby@openbsd.org>
+ * 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 <sys/types.h>
+#include <sys/queue.h>
+#include <sys/uio.h>
+
+#include <event.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <imsg.h>
+
+#include "ctl_parser.h"
+#include "kamid.h"
+
+enum token_type {
+	NOTOKEN,
+	ENDTOKEN,
+	KEYWORD,
+};
+
+
+struct token {
+	enum token_type		 type;
+	const char		*keyword;
+	int			 value;
+	const struct token	*next;
+};
+
+static const struct token t_main[];
+static const struct token t_log[];
+
+static const struct token t_main[] = {
+	{KEYWORD,	"reload",	RELOAD,		NULL},
+	{KEYWORD,	"log",		NONE,		t_log},
+	{ENDTOKEN,	"",		NONE,		NULL},
+};
+
+static const struct token t_log[] = {
+	{KEYWORD,	"verbose",	LOG_VERBOSE,	NULL},
+	{KEYWORD,	"brief",	LOG_BRIEF,	NULL},
+	{ENDTOKEN,	"",		NONE,		NULL},
+};
+
+static const struct token *match_token(const char *, const struct token *,
+    struct parse_result *);
+static void show_valid_args(const struct token *);
+
+struct parse_result *
+parse(int argc, char **argv)
+{
+	static struct parse_result	 res;
+	const struct token		*table = t_main;
+	const struct token		*match;
+
+	memset(&res, 0, sizeof(res));
+
+	while (argc >= 0) {
+		if ((match = match_token(argv[0], table, &res)) == NULL) {
+			fprintf(stderr, "valid commands/args:\n");
+			show_valid_args(table);
+			return NULL;
+		}
+
+		argc--;
+		argv++;
+
+		if (match->type == NOTOKEN || match->next == NULL)
+			break;
+
+		table = match->next;
+	}
+
+	if (argc > 0) {
+		fprintf(stderr, "superfluous argument: %s\n", argv[0]);
+		return NULL;
+	}
+
+	return &res;
+}
+
+static const struct token *
+match_token(const char *word, const struct token *table,
+    struct parse_result *res)
+{
+	size_t			 i, match;
+	const struct token	*t = NULL;
+
+	match = 0;
+
+	for (i = 0; table[i].type != ENDTOKEN; i++) {
+		switch (table[i].type) {
+		case NOTOKEN:
+			if (word == NULL || strlen(word) == 0) {
+				match++;
+				t = &table[i];
+			}
+			break;
+		case KEYWORD:
+			if (word != NULL && strncmp(word, table[i].keyword,
+			    strlen(word)) == 0) {
+				match++;
+				t = &table[i];
+				if (t->value)
+					res->action = t->value;
+			}
+			break;
+		case ENDTOKEN:
+			break;
+		}
+	}
+
+	if (match != 1) {
+		if (word == NULL)
+			fprintf(stderr, "missing argument:\n");
+		else if (match > 1)
+			fprintf(stderr, "ambiuous argument: %s\n", word);
+		else if (match < 1)
+			fprintf(stderr, "unknown argument: %s\n", word);
+		return NULL;
+	}
+
+	return t;
+}
+
+static void
+show_valid_args(const struct token *table)
+{
+	int i;
+
+	for (i = 0; table[i].type != ENDTOKEN; i++) {
+		switch (table[i].type) {
+		case NOTOKEN:
+			fprintf(stderr, "  <cr>\n");
+			break;
+		case KEYWORD:
+			fprintf(stderr, "  %s\n", table[i].keyword);
+			break;
+		case ENDTOKEN:
+			break;
+		}
+	}
+}
blob - /dev/null
blob + 1c41496f316a505b67099be11f5f5d3deec51a7b (mode 644)
--- /dev/null
+++ kamictl/ctl_parser.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2004 Esben Norby <norby@openbsd.org>
+ * 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 CTL_PARSER_H
+#define CTL_PARSER_H
+
+enum actions {
+	NONE,
+	LOG_VERBOSE,
+	LOG_BRIEF,
+	RELOAD,
+};
+
+struct parse_result {
+	enum actions	action;
+};
+
+struct parse_result	*parse(int, char **);
+
+#endif
blob - /dev/null
blob + b263ba9f41d13275d28d6a215aa1419302259a89 (mode 644)
--- /dev/null
+++ kamictl/kamictl.8
@@ -0,0 +1,61 @@
+.\" 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.
+.\"
+.Dd $Mdocdate: July 07 2021 $
+.Dt KAMICTL 8
+.Os
+.Sh NAME
+.Nm kamictl
+.Nd control the kamid daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl s Ar socket
+.Ar command
+.Op Ar argument ...
+.Sh DESCRIPTION
+The
+.Nm
+program controls the
+.Xr kamid 8
+daemon.
+.Pp
+The following options are available:
+.Bl -tag -width Ds
+.It Fl s Ar socket
+Use
+.Ar socket
+instead of the default
+.Pa /var/run/kamid.sock
+to communicate with
+.Xr kamid 8 .
+.El
+.Pp
+The following commands are available:
+.Bl -tag -width Ds
+.It Cm log brief
+Disable verbose debug logging.
+.It Cm log verbose
+Enable verbose debug logging.
+.It Cm reload
+Reload the configuration file.
+.El
+.Sh FILES
+.Bl -tag -width "/var/run/kamid.sockXX" -compact
+.It Pa /var/run/kamid.sock
+UNIX-domain socket used for communication with
+.Xr kamid 8 .
+.El
+.Sh SEE ALSO
+.Xr kamid.conf 5 ,
+.Xr kamid 8
blob - /dev/null
blob + be14f113641e49a974eaa5f420de82e4b67dccfa (mode 644)
--- /dev/null
+++ kamictl/kamictl.c
@@ -0,0 +1,145 @@
+/*
+ * 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 <sys/uio.h>
+#include <sys/un.h>
+
+#include <err.h>
+#include <event.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <imsg.h>
+
+#include "ctl_parser.h"
+#include "kamid.h"
+#include "log.h"
+
+__dead void	 usage(void);
+
+struct imsgbuf	*ibuf;
+
+__dead void
+usage(void)
+{
+	/*
+	 * XXX: this will print `kamid' if compat/getprogname.c is
+	 * used.
+	 */
+	fprintf(stderr, "usage: %s [-s socket] command [argument ...]\n",
+	    getprogname());
+	exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+	struct sockaddr_un	 sun;
+	struct parse_result	*res;
+	struct imsg		 imsg;
+	int			 ctl_sock;
+	int			 done = 0;
+	int			 n, verbose = 0;
+	int			 ch;
+	const char		*sockname;
+
+	log_init(1, LOG_DAEMON); /* Log to stderr. */
+
+	sockname = KD_SOCKET;
+	while ((ch = getopt(argc, argv, "s:")) != -1) {
+		switch (ch) {
+		case 's':
+			sockname = optarg;
+			break;
+		default:
+			usage();
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	/* parse command line */
+	if ((res = parse(argc, argv)) == NULL)
+		exit(1);
+
+	/* connect to control socket */
+	if ((ctl_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
+		err(1, "socket");
+
+	memset(&sun, 0, sizeof(sun));
+	sun.sun_family = AF_UNIX;
+	strlcpy(sun.sun_path, sockname, sizeof(sun.sun_path));
+
+	if (connect(ctl_sock, (struct sockaddr*)&sun, sizeof(sun)) == -1)
+		err(1, "connect: %s", sockname);
+
+#ifdef __OpenBSD__
+	if (pledge("stdio", NULL) == -1)
+		err(1, "pledge");
+#endif
+
+	if ((ibuf = calloc(1, sizeof(*ibuf))) == NULL)
+		err(1, NULL);
+	imsg_init(ibuf, ctl_sock);
+	done = 0;
+
+	/* process user request */
+	switch (res->action) {
+	case LOG_VERBOSE:
+		verbose = 1;
+		/* fallthrough */
+	case LOG_BRIEF:
+		imsg_compose(ibuf, IMSG_CTL_LOG_VERBOSE, 0, 0, -1,
+		    &verbose, sizeof(verbose));
+		puts("logging request sent.");
+		done = 1;
+		break;
+	case RELOAD:
+		imsg_compose(ibuf, IMSG_CTL_RELOAD, 0, 0, -1, NULL, 0);
+		puts("reload request sent.");
+		done = 1;
+		break;
+	default:
+		usage();
+	}
+
+	imsg_flush(ibuf);
+
+	/*
+	 * Later we may add commands which requires a response.
+	 */
+	while (!done) {
+		if ((n = imsg_get(ibuf, &imsg)) == -1)
+			errx(1, "imsg_get error");
+		if (n == 0)
+			break;
+
+		switch (res->action) {
+		default:
+			break;
+		}
+		imsg_free(&imsg);
+	}
+	close(ctl_sock);
+	free(ibuf);
+
+	return 0;
+}
blob - 70027cbf3cf809c67ca8b5cff38df304aa29bcf6 (mode 755)
blob + /dev/null
--- bootstrap
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-
-exec autoreconf -vfi
blob - /dev/null
blob + 13e41c7900a696a6570e9a462d3bef9d850745a0 (mode 644)
--- /dev/null
+++ kamid/9p.7
@@ -0,0 +1,393 @@
+.\" 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.
+.\"
+.Dd $Mdocdate: July 30 2021 $
+.Dt 9P 7
+.Os
+.Sh NAME
+.Nm 9P
+.Nd Simple Distributed File System
+.Sh DESCRIPTION
+.Nm
+is a protocol that implements a distributed file systems.
+It provides primitives to manage
+.Pq create, read, write and delete
+sets of files remotely.
+These files don't necessarily need to be actually stored on a disk,
+they may be, for example, synthesise on demand from external sources.
+.Pp
+A client transmits requests
+.Pq T-messages
+to a server, which returns replies
+.Pq R-messages
+to the client.
+The combined acts of transmitting a request of a particular type and
+receiving a reply is called a transaction of that type.
+.Pp
+Each message consists of a sequence of bytes mostly grouped in one,
+two or four integer fields transmitted in little-endian order
+.Pq least significant byte first .
+Data items of larger or variable lengths are represented by a two-byte
+field specifying the length followed by the actual data.
+The only exception to this rule are QIDs, thirteen byte long
+objects, that are sent as-is.
+.Pp
+Text strings are represented with a two-byte count and the sequence of
+UNICODE codepoints encoded in UTF-8.
+Text strings in 9p are not NUL-terminated.
+The NUL-terminator is illegal in all text strings and thus excluded
+from paths, user names and so on.
+.Pp
+Fields are hereafter denoted as
+.Bd -literal -offset indent
+type[1] tag[2] fid[4]
+.Ed
+.Pp
+to indicate that type is one byte long, tag two and fid four.
+Strings are denoted as name[s] and are sent on the wire as
+.Bd -literal -offset indent
+length[2] string[length]
+.Ed
+.Pp
+A qid, described later, is a 13-byte value that is sent on the wire as
+.Bd -literal -offset indent
+type[1] version[4] path[8]
+.Ed
+.Sh MESSAGE STRUCTURE
+Every message has a header with the following fields:
+.Bd -literal -offset indent
+len[4] type[1] tag[2]
+.Ed
+.Pp
+where len indicates the overall length of the message, including
+itself; type is one byte indicating the type of the message and the
+tag is a number choosen by the client that indicate uniquely the
+request.
+Then follows an optional body whose structure depends on the type of
+the message.
+.Pp
+The message types are as follows:
+.Pq the header is omitted for brevity
+.Bl -tag -width versionxx
+.It Ic version
+Negotiate the version and maximum message size.
+.Bd -literal
+msize[4] version[s]
+msize[4] version[s]
+.Ed
+.Pp
+The
+.Ic version
+request must be the first message sent, and the client cannot issue
+further requests until receiving the Rversion reply.
+.Cm tag
+should be
+.Dv NOTAG
+.Pq \-1 or 255 .
+The client suggest a
+.Cm msize
+.Pq the maximum size for packets
+and the protocol version used, the server replies with a
+.Cm msize
+smaller or equal to the one proposed by the client.
+The version string must always begin with the two character
+.Dq 9P .
+If the server don't understand the client required version, should
+reply with a Rversion using the version string
+.Dq unknown
+and not use a Rerror.
+.It Ic attach
+Populate the namespace
+.Bd -literal
+fid[4] afid[4] uname[s] aname[s]
+qid[13]
+.Ed
+.Pp
+The
+.Ic attach
+message binds the given
+.Ar fid
+to the root of the file tree identified by
+.Ar aname .
+.Ar uname
+identifies the user and
+.Ar afid
+specifies a fid previously established by an auth message, or the
+special
+.Dv NOFID
+value
+.Pq defined as (u32int)~0
+if the authentication is not required.
+.It Ic clunk
+Close fids.
+.Bd -literal
+fid[4]
+.Aq empty response
+.Ed
+.Pp
+Once a fid has been clunked
+.Pq closed
+it becomes
+.Dq free
+and the same value can be used for subsequential
+.Ic walk
+or
+.Ic attach
+requests.
+.Pp
+The actual file on the disk is not remove unless it was opened with the
+.Dv ORCLOSE
+flag.
+.It Ic error
+Return an error string.
+.Bd -literal
+ename[s]
+.Ed
+.Pp
+The Rerror message is used to return an error string describing the
+failure of a request.
+The
+.Cm tag
+indicates the failed request.
+.Pp
+Note that there isn't a
+.Ic Terror
+request for obvious reason and it's not possible for a server to reply to
+a
+.Ic Tversion
+or
+.Ic Tflush
+using
+.Ic Rerror .
+.It Ic flush
+Abort an ongoing operation.
+.Bd -literal
+oldtag[2]
+.Aq empty response
+.Ed
+.Pp
+Given the asynchronous nature of the protocol, the server may respond to
+the pending request before responding to the
+.Ic Tflush
+and is possible for a client to send multiple
+.Ic Tflush
+for the same operation.
+The client must wait to receive a corresponding
+.Ic Rflush
+before reusing
+.Ar oldtag
+for subsequent messages.
+.Pp
+If a response for
+.Ar oldtag
+is received before the
+.Ic Rflush
+reply, the client must assume that the operation was completed with success
+.Pq fid allocated, files created, ...
+If no response is received before the
+.Ic Rflush
+then the transaction is considered to have been successfully cancelled.
+.Pp
+Note that the tag of this request and the corresponding reply is NOT
+.Ar oldtag
+but a new tag value.
+.It Ic walk
+Traverse a file tree.
+.Bd -literal
+fid[4] newfid[4] nwname[2] nwname*(wname[s])
+nwqid[2] nwqid*(qid[13])
+.Ed
+.Pp
+The
+.Ar nwname
+components are walked in order starting from
+.Ar fid
+.Pq which must point to a directory
+and, if successful,
+.Ar newfid
+is associated to the reached file.
+.Pp
+It is possible for
+.Ar fid
+and
+.Ar newfid
+to be equal, in this case the fid is
+.Dq mutated ,
+otherwise
+.Ar newfid
+must be unused.
+As a special case, a walk of zero components duplicates the fid.
+.Pp
+If the first element cannot be walked for any reason an
+.Ic Rerror
+is returned.
+Otherwise,
+.Ic Rwalk
+is returned with a number of qids equal to the file viside by the walk.
+A client can thus detect a walk when that the replied
+.Ar nwqid
+number is not equal to the
+.Ar nwname
+field in the request.
+Only when walk return successfully
+.Ar newfid
+will be affected.
+.Pp
+A maximum of 16 component can be used per walk request.
+.It Ic open
+Prepare a fid for I/O.
+.Bd -literal
+fid[4] mode[1]
+qid[13] iounit[4]
+.Ed
+.Pp
+.Ar mode
+determines the type of I/O:
+.Bl -tag -width Ds -offset indent -compact
+.It 0 Dv OREAD
+.It 1 Dv OWRITE
+.It 2 Dv ORDWD
+.It 3 Dv OEXEC
+.El
+.Pp
+The returned
+.Ar iounit
+is the optimal blocksize for I/O.
+.It Ic create
+Create a file
+.Bd -literal
+fid[4] name[s] perm[4] mode[1]
+qid[13] iounit[4]
+.Ed
+.Pp
+The call attempts to create a file named
+.Ar name
+in the directory identified by
+.Ar fid
+according to
+.Ar perm
+and then to open it with
+.Ar mode
+into the given
+.Ar fid .
+.Pp
+It is illegal to use an already opened
+.Ar fid
+or to attempt to create the
+.Dq \&.
+or
+.Dq ..
+entries.
+.It Ic read
+Read data at offset from file
+.Bd -literal
+fid[4] offset[8] count[4]
+count[4] data[count]
+.Ed
+.Pp
+.Ar fid
+must have been prepared for I/O with a previous
+.Ic open
+call.
+The returned
+.Ar count
+is zero when reaching end-of-file and may be lesser than what requested.
+.Pp
+Directories are a stream of stat structures, as described in
+.Ic stat ,
+and for them the read request message must have offset equal to zero or
+the value of
+.Ar offset
+in the previous read on the directory plus the number of bytes returned
+in the previous read.
+Thus, is not possible to seek into directories except for rewinding.
+.It Ic write
+Write data at offset
+.Bd -literal
+fid[4] offset[8] count[4] data[count]
+count[4]
+.Ed
+.It Ic stat
+get file status
+.Bd -literal
+fid[4]
+stat[n]
+.Ed
+.Pp
+The stat structure is made by the following fields:
+.Bl -tag -width twelveletters -compact
+.It size[2]
+total byte count of the following data
+.It type[2]
+for kernel use
+.It dev[4]
+for kernel use
+.It qid[13]
+server unique identifier of the file
+.It mode[4]
+permissions and flags
+.It atime[4]
+last access time
+.It mtime[4]
+last modification time
+.It length[8]
+length of file in bytes
+.It name[s]
+file name
+(must be
+.Dq /
+if the file is the root directory of the server)
+.It uid[s]
+owner name
+.It gid[s]
+group name
+.It muid[s]
+name of the user who last modified the file.
+.El
+.It Ic remove
+Remove a file
+.Bd -literal
+fid[4]
+.Aq empty response
+.Ed
+.El
+.\" .Sh 9P2000.L EXTENSIONS
+.\" .Xr kamid 8
+.\" supports also a subset of the
+.\" .Sq 9P2000.L
+.\" dialect.
+.\" The supported messages are
+.\" .Bl -tag -width readdir
+.\" .It Ic readdir
+.\" Read directory entries
+.\" .Bd -literal
+.\" fid[4] offset[8] count[4]
+.\" count[4] data[count]
+.\" .Ed
+.\" .Pp
+.\" Each directory entry is described by a variable-length record:
+.\" .Ql qid[13] offset[8] type[1] name[s] .
+.\" Offset is zero upon the first call.
+.\" If the
+.\" .Ar count
+.\" field in the
+.\" .Ic Rreaddir
+.\" response is not zero then more data is available.
+.\" .Pp
+.\" .Ar count
+.\" is allowed to be zero in the request.
+.\" .El
+.Sh SEE ALSO
+.Xr utf8 7 ,
+.Xr kamid 8
blob - /dev/null
blob + ade79764eaa3d1b8896ff9c6e16ee466ae08cbc5 (mode 644)
--- /dev/null
+++ kamid/Makefile
@@ -0,0 +1,19 @@
+.PATH:${.CURDIR}/../lib
+
+.include "../kamid-version.mk"
+
+PROG=		kamid
+SRCS=		client.c control.c kamid.c listener.c log.c parse.y sandbox.c \
+		table.c table_static.c utils.c
+MAN=		kamid.conf.5 9p.7 kamid.8
+
+CPPFLAGS=	-I${.CURDIR}/../lib -I${.CURDIR}
+
+LDADD+= -levent -lutil -ltls
+DPADD+= ${LIBEVENT} ${LIBUTIL} ${LIBTLS}
+
+.if ${KAMID_RELEASE} != Yes
+NOMAN= Yes
+.endif
+
+.include <bsd.prog.mk>
blob - /dev/null
blob + 74734329db79092dc0f40c306b3c44998dd8cf27 (mode 644)
--- /dev/null
+++ kamid/client.c
@@ -0,0 +1,1628 @@
+/*
+ * 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/stat.h>
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/uio.h>
+
+#include <dirent.h>
+#include <endian.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <imsg.h>
+
+#include "client.h"
+#include "kami.h"
+#include "kamid.h"
+#include "log.h"
+#include "sandbox.h"
+#include "utils.h"
+
+/*
+ * XXX: atm is difficult to accept messages bigger than MAX_IMSGSIZE
+ * minus IMSG_HEADER_SIZE, we need something to split messages into
+ * chunks and receive them one by the other.
+ *
+ * CLIENT_MSIZE is thus the maximum message size we can handle now.
+ */
+#define CLIENT_MSIZE (MAX_IMSGSIZE - IMSG_HEADER_SIZE)
+
+#define DEBUG_PACKETS 0
+
+/* straight outta /src/usr.bin/ssh/scp.c */
+#define TYPE_OVERFLOW(type, val) \
+	((sizeof(type) == 4 && (val) > INT32_MAX) || \
+	 (sizeof(type) == 8 && (val) > INT64_MAX) || \
+	 (sizeof(type) != 4 && sizeof(type) != 8))
+
+STAILQ_HEAD(dirhead, dir) dirs;
+struct dir {
+	int			 refcount;
+	int			 fd;
+	STAILQ_ENTRY(dir)	 entries;
+};
+
+STAILQ_HEAD(fidhead, fid) fids;
+struct fid {
+	uint32_t		 fid;
+
+	char			 fpath[PATH_MAX];
+
+	/*
+	 * the flags passed to open(2).  O_CLOEXEC means ORCLOSE, that
+	 * is to unlink the file upon Tclunk.
+	 */
+	int			 iomode;
+
+	/*
+	 * if fd is not -1 this fid was opened, fd represents its
+	 * file descriptor and iomode the flags passed to open(2).
+	 */
+	int			 fd;
+	DIR			*d;
+	struct evbuffer		*evb;
+
+	/*
+	 * expected offset for Tread against a directory.
+	 */
+	uint64_t		 offset;
+
+	struct qid		 qid;
+	struct dir		*dir;
+	STAILQ_ENTRY(fid)	 entries;
+};
+
+static struct imsgev	*iev_listener;
+static struct evbuffer	*evb;
+static uint32_t		 peerid;
+
+static int		 handshaked;
+uint32_t		 msize;
+
+static __dead void	client_shutdown(void);
+static void		client_sig_handler(int, short, void *);
+static void		client_dispatch_listener(int, short, void *);
+static void		client_privdrop(const char *, const char *);
+
+static int		client_send_listener(int, const void *, uint16_t);
+
+static void		 qid_update_from_sb(struct qid *, struct stat *);
+
+static struct dir	*new_dir(int);
+static struct dir	*dir_incref(struct dir *);
+static void		 dir_decref(struct dir *);
+
+static struct fid	*new_fid(struct dir *, uint32_t, const char *, struct qid *);
+static struct fid	*fid_by_id(uint32_t);
+static void		 free_fid(struct fid *);
+
+static void		parse_message(const uint8_t *, size_t,
+			    struct np_msg_header *, uint8_t **);
+
+static void		np_write16(struct evbuffer *, uint16_t);
+static void		np_write32(struct evbuffer *, uint32_t);
+static void		np_write64(struct evbuffer *, uint64_t);
+static void		np_header(uint32_t, uint8_t, uint16_t);
+static void		np_string(struct evbuffer *, uint16_t, const char *);
+static void		np_qid(struct evbuffer *, struct qid *);
+static void		do_send(void);
+
+static void		np_version(uint16_t, uint32_t, const char *);
+static void		np_attach(uint16_t, struct qid *);
+static void		np_clunk(uint16_t);
+static void		np_flush(uint16_t);
+static void		np_walk(uint16_t, int, struct qid *);
+static void		np_open(uint16_t, struct qid *, uint32_t);
+static void		np_create(uint16_t, struct qid *, uint32_t);
+static void		np_read(uint16_t, uint32_t, void *);
+static void		np_write(uint16_t, uint32_t);
+static void		np_stat(uint16_t, uint32_t, void *);
+static void		np_remove(uint16_t);
+static void		np_error(uint16_t, const char *);
+static void		np_errno(uint16_t);
+
+static int	np_read8(const char *, const char *, uint8_t *,
+		    const uint8_t **, size_t *);
+static int	np_read16(const char *, const char *, uint16_t *,
+		    const uint8_t **, size_t *);
+static int	np_read32(const char *, const char *, uint32_t *,
+		    const uint8_t **, size_t *);
+static int	np_read64(const char *, const char *, uint64_t *,
+		    const uint8_t **, size_t *);
+
+#define READSTRERR	-1
+#define READSTRTRUNC	-2
+static int	np_readstr(const char *, const char *, char *, size_t,
+		    const uint8_t **, size_t *);
+
+#define NPREAD8(f, dst, src, len)  np_read8(__func__, f, dst, src, len)
+#define NPREAD16(f, dst, src, len) np_read16(__func__, f, dst, src, len)
+#define NPREAD32(f, dst, src, len) np_read32(__func__, f, dst, src, len)
+#define NPREAD64(f, dst, src, len) np_read64(__func__, f, dst, src, len)
+
+#define NPREADSTR(f, b, bl, src, len) np_readstr(__func__, f, b, bl, src, len)
+
+static void	tversion(struct np_msg_header *, const uint8_t *, size_t);
+static void	tattach(struct np_msg_header *, const uint8_t *, size_t);
+static void	tclunk(struct np_msg_header *, const uint8_t *, size_t);
+static void	tflush(struct np_msg_header *, const uint8_t *, size_t);
+static void	twalk(struct np_msg_header *, const uint8_t *, size_t);
+static void	topen(struct np_msg_header *, const uint8_t *, size_t);
+static void	tcreate(struct np_msg_header *, const uint8_t *, size_t);
+static void	tread(struct np_msg_header *, const uint8_t *, size_t);
+static void	twrite(struct np_msg_header *, const uint8_t *, size_t);
+static void	tstat(struct np_msg_header *, const uint8_t *, size_t);
+static void	tremove(struct np_msg_header *, const uint8_t *, size_t);
+static void	handle_message(struct imsg *, size_t);
+
+__dead void
+client(int debug, int verbose)
+{
+	struct event	ev_sigint, ev_sigterm;
+
+	log_init(debug, LOG_DAEMON);
+	log_setverbose(verbose);
+
+	setproctitle("client");
+	log_procinit("client");
+
+	log_debug("warming up");
+
+	event_init();
+
+	/* Setup signal handlers */
+	signal_set(&ev_sigint, SIGINT, client_sig_handler, NULL);
+	signal_set(&ev_sigterm, SIGTERM, client_sig_handler, NULL);
+
+	signal_add(&ev_sigint, NULL);
+	signal_add(&ev_sigterm, NULL);
+
+	signal(SIGPIPE, SIG_IGN);
+	signal(SIGHUP, SIG_IGN);
+
+	/* Setup pipe and event handler to the listener process */
+	if ((iev_listener = malloc(sizeof(*iev_listener))) == NULL)
+		fatal(NULL);
+
+	imsg_init(&iev_listener->ibuf, 3);
+	iev_listener->handler = client_dispatch_listener;
+
+	/* Setup event handlers. */
+	iev_listener->events = EV_READ;
+	event_set(&iev_listener->ev, iev_listener->ibuf.fd,
+	    iev_listener->events, iev_listener->handler, iev_listener);
+	event_add(&iev_listener->ev, NULL);
+
+	event_dispatch();
+	client_shutdown();
+}
+
+static __dead void
+client_shutdown(void)
+{
+	if (evb != NULL)
+		evbuffer_free(evb);
+
+	msgbuf_clear(&iev_listener->ibuf.w);
+	close(iev_listener->ibuf.fd);
+
+        free(iev_listener);
+
+	log_debug("client exiting");
+	exit(0);
+}
+
+static void
+client_sig_handler(int sig, short event, void *d)
+{
+	/*
+	 * Normal signal handler rules don't apply because libevent
+	 * decouples for us.
+	 */
+
+	switch (sig) {
+	case SIGINT:
+	case SIGTERM:
+		client_shutdown();
+	default:
+		fatalx("unexpected signal %d", sig);
+	}
+}
+
+#define AUTH_NONE 0
+#define AUTH_USER 1
+#define AUTH_DONE 2
+
+static void
+client_dispatch_listener(int fd, short event, void *d)
+{
+	static int		 auth = AUTH_NONE;
+	static char		 username[64] = {0};
+	static char		 dir[PATH_MAX] = {0};
+	struct imsg		 imsg;
+	struct imsgev		*iev = d;
+	struct imsgbuf		*ibuf;
+	ssize_t			 n;
+	int			 shut = 0;
+
+	ibuf = &iev->ibuf;
+
+	if (event & EV_READ) {
+		if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+			fatal("imsg_read error");
+		if (n == 0)	/* Connection closed */
+			shut = 1;
+	}
+	if (event & EV_WRITE) {
+		if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+			fatal("msgbuf_write");
+		if (n == 0)	/* Connection closed */
+			shut = 1;
+	}
+
+	for (;;) {
+		if ((n = imsg_get(ibuf, &imsg)) == -1)
+			fatal("%s: imsg_get error", __func__);
+		if (n == 0)	/* No more messages. */
+			break;
+
+		switch (imsg.hdr.type) {
+		case IMSG_AUTH:
+			peerid = imsg.hdr.peerid;
+			if (auth)
+				fatalx("%s: IMSG_AUTH already done", __func__);
+			auth = AUTH_USER;
+			((char *)imsg.data)[IMSG_DATA_SIZE(imsg)-1] = '\0';
+			strlcpy(username, imsg.data, sizeof(username));
+			break;
+		case IMSG_AUTH_DIR:
+			if (auth != AUTH_USER)
+				fatalx("%s: IMSG_AUTH_DIR not after IMSG_AUTH",
+				    __func__);
+			auth = AUTH_DONE;
+			((char *)imsg.data)[IMSG_DATA_SIZE(imsg)-1] = '\0';
+			strlcpy(dir, imsg.data, sizeof(dir));
+			client_privdrop(username, dir);
+			memset(username, 0, sizeof(username));
+			memset(dir, 0, sizeof(username));
+			break;
+		case IMSG_BUF:
+			/* echo! */
+			if (!auth)
+				fatalx("%s: can't handle messages before"
+				    " doing the auth", __func__);
+			handle_message(&imsg, IMSG_DATA_SIZE(imsg));
+			break;
+		case IMSG_CONN_GONE:
+			log_debug("closing");
+			shut = 1;
+			break;
+		default:
+			log_debug("%s: unexpected imsg %d",
+			    __func__, imsg.hdr.type);
+			break;
+		}
+		imsg_free(&imsg);
+	}
+
+	if (!shut)
+		imsg_event_add(iev);
+	else {
+		/* This pipe is dead.  Remove its event handler. */
+		event_del(&iev->ev);
+		log_debug("pipe closed, shutting down...");
+		event_loopexit(NULL);
+	}
+}
+
+static void
+client_privdrop(const char *username, const char *dir)
+{
+	struct passwd *pw;
+
+	setproctitle("client %s", username);
+
+	if ((pw = getpwnam(username)) == NULL)
+		fatalx("getpwnam(%s) failed", username);
+
+	if (chroot(dir) == -1)
+		fatal("chroot");
+	if (chdir("/") == -1)
+		fatal("chdir(\"/\")");
+
+	if (setgroups(1, &pw->pw_gid) ||
+	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
+	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
+		fatal("can't drop privileges");
+
+	sandbox_client();
+	log_debug("client ready; user=%s dir=%s", username, dir);
+
+	if ((evb = evbuffer_new()) == NULL)
+		fatal("evbuffer_new");
+}
+
+static int
+client_send_listener(int type, const void *data, uint16_t len)
+{
+	int ret;
+
+	if ((ret = imsg_compose(&iev_listener->ibuf, type, peerid, 0, -1,
+	    data, len)) != -1)
+		imsg_event_add(iev_listener);
+
+	return ret;
+}
+
+/* set qid fields from sb */
+static void
+qid_update_from_sb(struct qid *qid, struct stat *sb)
+{
+	qid->path = sb->st_ino;
+
+	/*
+	 * Theoretically (and hopefully!) this should be a 64 bit
+	 * number.  Unfortunately, 9P uses 32 bit timestamps.
+	 */
+	qid->vers = sb->st_mtim.tv_sec;
+
+	if (S_ISREG(sb->st_mode))
+		qid->type = QTFILE;
+	else if (S_ISDIR(sb->st_mode))
+		qid->type = QTDIR;
+	else if (S_ISLNK(sb->st_mode))
+		qid->type = QTSYMLINK;
+}
+
+/* creates a qid given a fd */
+static struct dir *
+new_dir(int fd)
+{
+	struct dir	*dir;
+
+	if ((dir = calloc(1, sizeof(*dir))) == NULL)
+		return NULL;
+
+	dir->fd = fd;
+	STAILQ_INSERT_HEAD(&dirs, dir, entries);
+	return dir;
+}
+
+static struct dir *
+dir_incref(struct dir *dir)
+{
+	dir->refcount++;
+	return dir;
+}
+
+static void
+dir_decref(struct dir *dir)
+{
+	if (--dir->refcount > 0)
+		return;
+
+	STAILQ_REMOVE(&dirs, dir, dir, entries);
+
+	close(dir->fd);
+	free(dir);
+}
+
+static struct fid *
+new_fid(struct dir *dir, uint32_t fid, const char *path, struct qid *qid)
+{
+	struct fid	*f;
+	struct qid	 q;
+	struct stat	sb;
+
+	if (qid == NULL) {
+		if (fstatat(dir->fd, path, &sb, 0)) {
+			log_warn("fstatat(%s)", path);
+			return NULL;
+		}
+		qid_update_from_sb(&q, &sb);
+		qid = &q;
+	}
+
+	if ((f = calloc(1, sizeof(*f))) == NULL)
+		return NULL;
+
+	f->dir = dir_incref(dir);
+	f->fid = fid;
+	f->fd = -1;
+
+	strlcpy(f->fpath, path, sizeof(f->fpath));
+
+	memcpy(&f->qid, qid, sizeof(f->qid));
+
+	STAILQ_INSERT_HEAD(&fids, f, entries);
+
+	return f;
+}
+
+static struct fid *
+fid_by_id(uint32_t fid)
+{
+	struct fid	*f;
+
+	STAILQ_FOREACH(f, &fids, entries) {
+		if (f->fid == fid)
+			return f;
+	}
+
+	return NULL;
+}
+
+static void
+free_fid(struct fid *f)
+{
+	int r;
+
+	if (f->fd != -1) {
+		if (f->d != NULL)
+			r = closedir(f->d);
+		else
+			r = close(f->fd);
+
+		if (r == -1)
+			fatal("can't close fid %d", f->fid);
+
+		if (f->evb != NULL)
+			evbuffer_free(f->evb);
+
+		/* try to honour ORCLOSE if requested */
+		if (f->iomode & O_CLOEXEC)
+			unlinkat(f->dir->fd, f->fpath, 0);
+	}
+
+	dir_decref(f->dir);
+
+	STAILQ_REMOVE(&fids, f, fid, entries);
+	free(f);
+}
+
+static void
+parse_message(const uint8_t *data, size_t len, struct np_msg_header *hdr,
+    uint8_t **cnt)
+{
+	size_t olen = len;
+
+	if (!NPREAD32("len", &hdr->len, &data, &len) ||
+	    !NPREAD8("type", &hdr->type, &data, &len) ||
+	    !NPREAD16("tag", &hdr->tag, &data, &len))
+		goto err;
+
+	if (olen != hdr->len)
+		goto err;
+
+	if (hdr->type < Tversion ||
+	    hdr->type >= Tmax    ||
+	    hdr->type == Terror  ||
+	    (hdr->type & 0x1) != 0) /* cannot recv a R* */
+		goto err;
+
+	hdr->tag = le32toh(hdr->tag);
+
+	*cnt = (uint8_t *)data;
+	return;
+
+err:
+	/* TODO: send a proper message to terminate the connection. */
+	fatalx("got invalid message");
+}
+
+static void
+np_write16(struct evbuffer *e, uint16_t x)
+{
+	x = htole16(x);
+	evbuffer_add(e, &x, sizeof(x));
+}
+
+static void
+np_write32(struct evbuffer *e, uint32_t x)
+{
+	x = htole32(x);
+	evbuffer_add(e, &x, sizeof(x));
+}
+
+static void
+np_write64(struct evbuffer *e, uint64_t x)
+{
+	x = htole64(x);
+	evbuffer_add(e, &x, sizeof(x));
+}
+
+static void
+np_writebuf(struct evbuffer *e, size_t len, void *data)
+{
+	evbuffer_add(e, data, len);
+}
+
+static void
+np_header(uint32_t len, uint8_t type, uint16_t tag)
+{
+	len += HEADERSIZE;
+
+	len = htole32(len);
+	tag = htole16(tag);
+
+	evbuffer_add(evb, &len, sizeof(len));
+	evbuffer_add(evb, &type, sizeof(type));
+	evbuffer_add(evb, &tag, sizeof(tag));
+}
+
+static void
+np_string(struct evbuffer *e, uint16_t len, const char *str)
+{
+	uint16_t l = len;
+
+	len = htole16(len);
+	evbuffer_add(e, &len, sizeof(len));
+	evbuffer_add(e, str, l);
+}
+
+static void
+np_qid(struct evbuffer *e, struct qid *qid)
+{
+	uint64_t	path;
+	uint32_t	vers;
+
+	path = htole64(qid->path);
+	vers = htole32(qid->vers);
+
+	evbuffer_add(e, &qid->type, sizeof(qid->type));
+	evbuffer_add(e, &vers, sizeof(vers));
+	evbuffer_add(e, &path, sizeof(path));
+}
+
+static void
+do_send(void)
+{
+	size_t	 len;
+	void	*data;
+
+	len = EVBUFFER_LENGTH(evb);
+	data = EVBUFFER_DATA(evb);
+
+#if DEBUG_PACKETS
+	hexdump("outgoing packet", data, len);
+#endif
+	client_send_listener(IMSG_BUF, data, len);
+	evbuffer_drain(evb, len);
+}
+
+static void
+np_version(uint16_t tag, uint32_t msize, const char *version)
+{
+	uint16_t l;
+
+	l = strlen(version);
+
+	msize = htole32(msize);
+
+	np_header(sizeof(msize) + sizeof(l) + l, Rversion, tag);
+	evbuffer_add(evb, &msize, sizeof(msize));
+	np_string(evb, l, version);
+	do_send();
+}
+
+static void
+np_attach(uint16_t tag, struct qid *qid)
+{
+	np_header(QIDSIZE, Rattach, tag);
+	np_qid(evb, qid);
+	do_send();
+}
+
+static void
+np_clunk(uint16_t tag)
+{
+	np_header(0, Rclunk, tag);
+	do_send();
+}
+
+static void
+np_flush(uint16_t tag)
+{
+	np_header(0, Rflush, tag);
+	do_send();
+}
+
+static void
+np_walk(uint16_t tag, int nwqid, struct qid *wqid)
+{
+	int i;
+
+	/* two bytes for the counter */
+	np_header(2 + QIDSIZE * nwqid, Rwalk, tag);
+	np_write16(evb, nwqid);
+	for (i = 0; i < nwqid; ++i)
+		np_qid(evb, wqid + i);
+
+	do_send();
+}
+
+static void
+np_open(uint16_t tag, struct qid *qid, uint32_t iounit)
+{
+	np_header(QIDSIZE + sizeof(iounit), Ropen, tag);
+	np_qid(evb, qid);
+	np_write32(evb, iounit);
+	do_send();
+}
+
+static void
+np_create(uint16_t tag, struct qid *qid, uint32_t iounit)
+{
+	np_header(QIDSIZE + sizeof(iounit), Rcreate, tag);
+	np_qid(evb, qid);
+	np_write32(evb, iounit);
+	do_send();
+}
+
+static void
+np_read(uint16_t tag, uint32_t count, void *data)
+{
+	if (sizeof(count) + count + HEADERSIZE >= msize) {
+		np_error(tag, "Rread would overflow");
+		return;
+	}
+
+	np_header(sizeof(count) + count, Rread, tag);
+	np_write32(evb, count);
+	np_writebuf(evb, count, data);
+	do_send();
+}
+
+static void
+np_write(uint16_t tag, uint32_t count)
+{
+	np_header(sizeof(count), Rwrite, tag);
+	np_write32(evb, count);
+	do_send();
+}
+
+static void
+np_stat(uint16_t tag, uint32_t count, void *data)
+{
+	if (sizeof(count) + count + HEADERSIZE >= msize) {
+		np_error(tag, "Rstat would overflow");
+		return;
+	}
+
+	np_header(count, Rstat, tag);
+	np_writebuf(evb, count, data);
+	do_send();
+}
+
+static void
+np_remove(uint16_t tag)
+{
+	np_header(0, Rremove, tag);
+	do_send();
+}
+
+static void
+np_error(uint16_t tag, const char *errstr)
+{
+	uint16_t l;
+
+	l = strlen(errstr);
+
+	np_header(sizeof(l) + l, Rerror, tag);
+	np_string(evb, l, errstr);
+	do_send();
+}
+
+static void
+np_errno(uint16_t tag)
+{
+	int saved_errno;
+	char buf[NL_TEXTMAX] = {0};
+
+	saved_errno = errno;
+
+	strerror_r(errno, buf, sizeof(buf));
+	np_error(tag, buf);
+
+	errno = saved_errno;
+}
+
+static int
+np_read8(const char *t, const char *f, uint8_t *dst, const uint8_t **src,
+    size_t *len)
+{
+	if (*len < sizeof(*dst)) {
+		log_warnx("%s: wanted %zu bytes for the %s field but only "
+		    "%zu are available.", t, sizeof(*dst), f, *len);
+		return -1;
+	}
+
+	memcpy(dst, *src, sizeof(*dst));
+	*src += sizeof(*dst);
+	*len -= sizeof(*dst);
+
+	return 1;
+}
+
+static int
+np_read16(const char *t, const char *f, uint16_t *dst, const uint8_t **src,
+    size_t *len)
+{
+	if (*len < sizeof(*dst)) {
+		log_warnx("%s: wanted %zu bytes for the %s field but only "
+		    "%zu are available.", t, sizeof(*dst), f, *len);
+		return -1;
+	}
+
+	memcpy(dst, *src, sizeof(*dst));
+	*src += sizeof(*dst);
+	*len -= sizeof(*dst);
+	*dst = le16toh(*dst);
+
+	return 1;
+}
+
+static int
+np_read32(const char *t, const char *f, uint32_t *dst, const uint8_t **src,
+    size_t *len)
+{
+	if (*len < sizeof(*dst)) {
+		log_warnx("%s: wanted %zu bytes for the %s field but only "
+		    "%zu are available.", t, sizeof(*dst), f, *len);
+		return -1;
+	}
+
+	memcpy(dst, *src, sizeof(*dst));
+	*src += sizeof(*dst);
+	*len -= sizeof(*dst);
+	*dst = le32toh(*dst);
+
+	return 1;
+}
+
+static int
+np_read64(const char *t, const char *f, uint64_t *dst, const uint8_t **src,
+    size_t *len)
+{
+	if (*len < sizeof(*dst)) {
+		log_warnx("%s: wanted %zu bytes for the %s field but only "
+		    "%zu are available.", t, sizeof(*dst), f, *len);
+		return -1;
+	}
+
+	memcpy(dst, *src, sizeof(*dst));
+	*src += sizeof(*dst);
+	*len -= sizeof(*dst);
+	*dst = le64toh(*dst);
+
+	return 1;
+}
+
+static int
+np_readstr(const char *t, const char *f, char *res, size_t reslen,
+    const uint8_t **src, size_t *len)
+{
+	uint16_t	sl;
+	char		buf[32];
+
+	strlcpy(buf, f, sizeof(buf));
+	strlcat(buf, "-len", sizeof(buf));
+
+	if (!np_read16(t, buf, &sl, src, len))
+		return READSTRERR;
+
+	if (*len < sl) {
+		log_warnx("%s: wanted %d bytes for the %s field but only "
+		    "%zu are available.", t, sl, f, *len);
+		return READSTRERR;
+	}
+
+	if (*len > reslen-1)
+		return READSTRTRUNC;
+
+	memcpy(res, *src, sl);
+	res[sl] = '\0';
+	*src += sl;
+	*len -= sl;
+
+	return 0;
+}
+
+static void
+tversion(struct np_msg_header *hdr, const uint8_t *data, size_t len)
+{
+	char *dot, version[32];
+
+	if (handshaked)
+		goto err;
+
+	/* msize[4] version[s] */
+	if (!NPREAD32("msize", &msize, &data, &len))
+		goto err;
+
+	switch (NPREADSTR("version", version, sizeof(version), &data, &len)) {
+	case READSTRERR:
+		goto err;
+	case READSTRTRUNC:
+		log_warnx("9P version string too long, truncated");
+		goto mismatch;
+	}
+
+	if ((dot = strchr(version, '.')) != NULL)
+		*dot = '\0';
+
+	if (strcmp(version, VERSION9P) != 0 ||
+	    msize == 0)
+		goto mismatch;
+
+	/* version matched */
+	handshaked = 1;
+	msize = MIN(msize, CLIENT_MSIZE);
+	client_send_listener(IMSG_MSIZE, &msize, sizeof(msize));
+	np_version(hdr->tag, msize, VERSION9P);
+	return;
+
+mismatch:
+	log_warnx("unknown 9P version string: \"%s\", want "VERSION9P,
+	    version);
+	np_version(hdr->tag, MSIZE9P, "unknown");
+	return;
+
+err:
+	client_send_listener(IMSG_CLOSE, NULL, 0);
+	client_shutdown();
+}
+
+static void
+tattach(struct np_msg_header *hdr, const uint8_t *data, size_t len)
+{
+	struct dir	*dir;
+	struct fid	*f;
+	uint32_t	 fid, afid;
+	int		 fd;
+	char		 aname[PATH_MAX];
+
+	/* fid[4] afid[4] uname[s] aname[s] */
+
+	if (!NPREAD32("fid", &fid, &data, &len) ||
+	    !NPREAD32("afid", &afid, &data, &len))
+		goto err;
+
+	/* read the uname but don't actually use it */
+	switch (NPREADSTR("uname", aname, sizeof(aname), &data, &len)) {
+	case READSTRERR:
+		goto err;
+	case READSTRTRUNC:
+		np_error(hdr->tag, "name too long");
+		return;
+	}
+
+	switch (NPREADSTR("aname", aname, sizeof(aname), &data, &len)) {
+	case READSTRERR:
+		goto err;
+	case READSTRTRUNC:
+		np_error(hdr->tag, "name too long");
+		return;
+	}
+
+	if (fid_by_id(fid) != NULL || afid != NOFID) {
+		np_error(hdr->tag, "invalid fid or afid");
+		return;
+	}
+
+	if ((fd = open(aname, O_RDONLY|O_DIRECTORY)) == -1)
+		goto fail;
+
+	if ((dir = new_dir(fd)) == NULL)
+		goto fail;
+
+	log_debug("attached %s to %d", aname, fid);
+
+	if ((f = new_fid(dir, fid, aname, NULL)) == NULL) {
+		dir_decref(dir);
+		goto fail;
+	}
+
+	np_attach(hdr->tag, &f->qid);
+	return;
+
+fail:
+	np_errno(hdr->tag);
+	log_warn("failed to attach %s", aname);
+	return;
+
+err:
+	client_send_listener(IMSG_CLOSE, NULL, 0);
+	client_shutdown();
+}
+
+static void
+tclunk(struct np_msg_header *hdr, const uint8_t *data, size_t len)
+{
+	struct fid	*f;
+	uint32_t	 fid;
+
+	/* fid[4] */
+	if (!NPREAD32("fid", &fid, &data, &len)) {
+		client_send_listener(IMSG_CLOSE, NULL, 0);
+		client_shutdown();
+		return;
+	}
+
+	if ((f = fid_by_id(fid)) == NULL) {
+		np_error(hdr->tag, "invalid fid");
+		return;
+	}
+
+	free_fid(f);
+	np_clunk(hdr->tag);
+}
+
+static void
+tflush(struct np_msg_header *hdr, const uint8_t *data, size_t len)
+{
+	uint16_t	oldtag;
+
+	/*
+	 * We're doing only synchronous I/O.  Tflush is implemented
+	 * only because it's illegal to reply with a Rerror.
+	 */
+
+	/* oldtag[2] */
+	if (len != sizeof(oldtag)) {
+		log_warnx("Tflush with the wrong size: got %zu want %zu",
+		    len, sizeof(oldtag));
+		client_send_listener(IMSG_CLOSE, NULL, 0);
+		client_shutdown();
+		return;
+	}
+
+	np_flush(hdr->tag);
+}
+
+static void
+twalk(struct np_msg_header *hdr, const uint8_t *data, size_t len)
+{
+	struct stat	 sb;
+	struct dir	*dir;
+	struct qid	 wqid[MAXWELEM] = {0};
+	struct fid	*f, *nf;
+	uint32_t	 fid, newfid;
+	uint16_t	 nwname;
+	int		 fd, oldfd, no, nwqid = 0;
+	char		 wnam[PATH_MAX];
+
+	if (!NPREAD32("fid", &fid, &data, &len)       ||
+	    !NPREAD32("newfid", &newfid, &data, &len) ||
+	    !NPREAD16("nwname", &nwname, &data, &len))
+		goto err;
+
+	if (nwname > MAXWELEM) {
+		log_warnx("Twalk: more than %d path elements: %d",
+		    MAXWELEM, nwname);
+		goto err;
+	}
+
+	if ((f = fid_by_id(fid)) == NULL) {
+		np_error(hdr->tag, "invalid fid");
+		return;
+	}
+
+	if (f->fd != -1) {
+		np_error(hdr->tag, "fid already opened for I/O");
+		return;
+	}
+
+	if (fid == newfid)
+		nf = f;
+	else if ((nf = fid_by_id(newfid)) != NULL) {
+		np_error(hdr->tag, "newfid already in use");
+		return;
+	} else
+		nf = NULL;
+
+	/* special case: fid duplication */
+	if (nwname == 0) {
+		/*
+		 * TODO: should we forbid fids duplication when fid ==
+		 * newfid?
+		 */
+		if (nf == NULL &&
+		    (nf = new_fid(f->dir, newfid, f->fpath, &f->qid)) == NULL)
+			fatal("new_fid duplication");
+
+		np_walk(hdr->tag, 0, NULL);
+		return;
+	}
+
+	if (!(f->qid.type & QTDIR)) {
+		np_error(hdr->tag, "fid doesn't represent a directory");
+		return;
+	}
+
+	oldfd = f->dir->fd;
+
+	for (nwqid = 0; nwqid < nwname; nwqid++) {
+		switch (NPREADSTR("wname", wnam, sizeof(wnam), &data, &len)) {
+		case READSTRERR:
+			goto err;
+		case READSTRTRUNC:
+			np_error(hdr->tag, "wname too long");
+			return;
+		}
+
+		if (*wnam == '\0' ||
+		    strchr(wnam, '/') != NULL ||
+		    !strcmp(wnam, ".")) {
+			errno = EINVAL;
+			goto cantopen;
+		}
+
+		if ((fd = openat(oldfd, wnam, O_RDONLY|O_DIRECTORY)) == -1 &&
+		    errno != ENOTDIR)
+			goto cantopen;
+
+		if ((fd == -1 && fstatat(oldfd, wnam, &sb, 0) == -1) ||
+		    (fd != -1 && fstat(fd, &sb) == -1))
+			goto cantopen;
+
+		qid_update_from_sb(&wqid[nwqid], &sb);
+
+		/* reached a file but we still have other components */
+		if (fd == -1 && nwqid+1 < nwname)
+			goto cantopen;
+
+		/* reached the end and found a file */
+		if (fd == -1 && nwqid+1 == nwname)
+			continue;
+
+		if (oldfd != f->dir->fd)
+			close(oldfd);
+		oldfd = fd;
+	}
+
+	/*
+	 * If fd is -1 we've reached a file, otherwise we've just
+	 * reached another directory.  We must pay attention to what
+	 * file descriptor we use to create the dir, because if we've
+	 * reached a file and oldfd is f->dir->fd then we *must* share
+	 * the same dir (it was a walk of one path from a directory to a
+	 * file, otherwise fun is bound to happen as soon as the client
+     	 * closes the fid for the directory but keeps the one for the
+	 * file.
+	 */
+	if (fd == -1 && oldfd == f->dir->fd)
+		dir = f->dir;
+	else if (fd == -1)
+		dir = new_dir(oldfd);
+	else
+		dir = new_dir(fd);
+
+	if (dir == NULL)
+		fatal("new_dir");
+
+	if (nf == NULL) {
+		if ((nf = new_fid(dir, newfid, wnam, &wqid[nwqid-1])) == NULL)
+			fatal("new fid");
+	} else {
+		/* update the dir */
+		dir_decref(nf->dir);
+		nf->dir = dir_incref(dir);
+	}
+
+	np_walk(hdr->tag, nwqid, wqid);
+	return;
+
+cantopen:
+	if (oldfd != f->dir->fd)
+		close(oldfd);
+	no = errno;
+	if (nwqid == 0)
+		np_error(hdr->tag, strerror(no));
+	else
+		np_walk(hdr->tag, nwqid, wqid);
+	return;
+
+err:
+	client_send_listener(IMSG_CLOSE, NULL, 0);
+	client_shutdown();
+}
+
+static inline int
+npmode_to_unix(uint8_t mode, int *flags)
+{
+	switch (mode & 0x0F) {
+	case KOREAD:
+		*flags = O_RDONLY;
+		break;
+	case KOWRITE:
+		*flags = O_WRONLY;
+		break;
+	case KORDWR:
+		*flags = O_RDWR;
+		break;
+	case KOEXEC:
+		log_warnx("tried to open something with KOEXEC");
+		/* fallthrough */
+	default:
+		return -1;
+	}
+
+	if (mode & KOTRUNC)
+		*flags |= O_TRUNC;
+	if (mode & KORCLOSE)
+		*flags |= O_CLOEXEC;
+
+	return 0;
+}
+
+static void
+topen(struct np_msg_header *hdr, const uint8_t *data, size_t len)
+{
+	struct stat	 sb;
+	struct qid	 qid;
+	struct fid	*f;
+	uint32_t	 fid;
+	uint8_t		 mode;
+	const char	*path;
+
+	/* fid[4] mode[1] */
+	if (!NPREAD32("fid", &fid, &data, &len) ||
+	    !NPREAD8("mode", &mode, &data, &len)) {
+		client_send_listener(IMSG_CLOSE, NULL, 0);
+		client_shutdown();
+		return;
+	}
+
+	if ((f = fid_by_id(fid)) == NULL || f->fd != -1) {
+		np_error(hdr->tag, "invalid fid");
+		return;
+	}
+
+	if (npmode_to_unix(mode, &f->iomode) == -1) {
+		np_error(hdr->tag, "invalid mode");
+		return;
+	}
+
+	path = f->fpath;
+	if (f->qid.type & QTDIR)
+		path = ".";
+
+	if ((f->fd = openat(f->dir->fd, path, f->iomode)) == -1) {
+		np_error(hdr->tag, strerror(errno));
+		return;
+	}
+
+	if (fstat(f->fd, &sb) == -1)
+		fatal("fstat");
+
+	if (S_ISDIR(sb.st_mode)) {
+		if ((f->d = fdopendir(f->fd)) == NULL) {
+			np_errno(hdr->tag);
+			close(f->fd);
+			f->fd = -1;
+			return;
+		}
+
+		if ((f->evb = evbuffer_new()) == NULL) {
+			np_errno(hdr->tag);
+			closedir(f->d);
+			f->d = NULL;
+			f->fd = -1;
+		}
+	}
+
+	f->offset = 0;
+
+	qid_update_from_sb(&qid, &sb);
+	np_open(hdr->tag, &qid, sb.st_blksize);
+}
+
+static void
+tcreate(struct np_msg_header *hdr, const uint8_t *data, size_t len)
+{
+	struct stat	 sb;
+	struct qid	 qid;
+	struct fid	*f;
+	uint32_t	 fid, perm;
+	uint8_t		 mode;
+	char		 name[PATH_MAX];
+
+	/* fid[4] name[s] perm[4] mode[1] */
+	if (!NPREAD32("fid", &fid, &data, &len))
+		goto err;
+	switch (NPREADSTR("name", name, sizeof(name), &data, &len)) {
+	case READSTRERR:
+		goto err;
+	case READSTRTRUNC:
+		np_error(hdr->tag, "name too long");
+		return;
+	}
+	if (!NPREAD32("perm", &perm, &data, &len) ||
+	    !NPREAD8("mode", &mode, &data, &len))
+		goto err;
+
+	if (!strcmp(name, ".") || !strcmp(name, "..") ||
+	    strchr(name, '/') != NULL) {
+		np_error(hdr->tag, "invalid name");
+		return;
+	}
+
+	if ((f = fid_by_id(fid)) == NULL || f->fd != -1) {
+		np_error(hdr->tag, "invalid fid");
+		return;
+	}
+
+	if (!(f->qid.type & QTDIR)) {
+		np_error(hdr->tag, "fid doesn't identify a directory");
+		return;
+	}
+
+	if (npmode_to_unix(mode, &f->iomode) == -1) {
+		np_error(hdr->tag, "invalid mode");
+		return;
+	}
+
+	if (f->iomode & O_RDONLY) {
+		np_error(hdr->tag, "can't create a read-only file");
+		return;
+	}
+
+	/* TODO: parse the mode */
+
+	if (perm & 0x80000000) {
+		/* create a directory */
+		f->fd = mkdirat(f->dir->fd, name, 0755);
+	} else {
+		/* create a file */
+		f->fd = openat(f->dir->fd, name, f->iomode | O_CREAT | O_TRUNC,
+		    0644);
+	}
+
+	if (f->fd == -1) {
+		np_errno(hdr->tag);
+		return;
+	}
+
+	if (fstat(f->fd, &sb) == -1)
+		fatal("fstat");
+
+	if (S_ISDIR(sb.st_mode)) {
+		if ((f->d = fdopendir(f->fd)) == NULL) {
+			np_errno(hdr->tag);
+			close(f->fd);
+			f->fd = -1;
+			return;
+		}
+
+		if ((f->evb = evbuffer_new()) == NULL) {
+			np_errno(hdr->tag);
+			closedir(f->d);
+			f->d = NULL;
+			f->fd = -1;
+		}
+	}
+
+	f->offset = 0;
+
+	qid_update_from_sb(&qid, &sb);
+	np_create(hdr->tag, &qid, sb.st_blksize);
+
+	return;
+
+err:
+	client_send_listener(IMSG_CLOSE, NULL, 0);
+	client_shutdown();
+}
+
+static inline void
+serialize_stat(const char *fname, struct stat *sb, struct evbuffer *evb)
+{
+	struct qid	 qid;
+	const char	*uid, *gid, *muid;
+	size_t		 tot;
+	uint16_t	 namlen, uidlen, gidlen, ulen;
+
+	qid_update_from_sb(&qid, sb);
+
+	/* TODO: fill these fields */
+	uid = "";
+	gid = "";
+	muid = "";
+
+	namlen = strlen(fname);
+	uidlen = strlen(uid);
+	gidlen = strlen(gid);
+	ulen = strlen(muid);
+
+	tot = NPSTATSIZ(namlen, uidlen, gidlen, ulen);
+	if (tot > UINT32_MAX) {
+		log_warnx("stat info for dir entry %s would overflow",
+		    fname);
+		return;
+	}
+
+	np_write16(evb, tot);			/*	size[2]		*/
+	np_write16(evb, sb->st_rdev);		/*	type[2]		*/
+	np_write32(evb, sb->st_dev);		/*	dev[4]		*/
+	np_qid(evb, &qid);			/*	qid[13]		*/
+
+	/* XXX: translate? */
+	np_write32(evb, sb->st_mode);		/*	mode[4]		*/
+
+	np_write32(evb, sb->st_atim.tv_sec);	/*	atime[4]	*/
+	np_write32(evb, sb->st_mtim.tv_sec);	/*	mtime[4]	*/
+	np_write64(evb, sb->st_size);		/*	length[8]	*/
+	np_string(evb, namlen, fname);		/*	name[s]		*/
+	np_string(evb, uidlen, uid);		/*	uid[s]		*/
+	np_string(evb, gidlen, gid);		/*	gid[s]		*/
+	np_string(evb, ulen, muid);		/*	muid[s]		*/
+}
+
+static void
+tread(struct np_msg_header *hdr, const uint8_t *data, size_t len)
+{
+	struct fid	*f;
+	ssize_t		 r;
+	size_t		 howmuch;
+	uint64_t	 off;
+	uint32_t	 fid, count;
+	char		 buf[2048];
+
+	/* fid[4] offset[8] count[4] */
+	if (!NPREAD32("fid", &fid, &data, &len) ||
+	    !NPREAD64("offset", &off, &data, &len) ||
+	    !NPREAD32("count", &count, &data, &len)) {
+		client_send_listener(IMSG_CLOSE, NULL, 0);
+		client_shutdown();
+		return;
+	}
+
+	if ((f = fid_by_id(fid)) == NULL || f->fd == -1) {
+		np_error(hdr->tag, "invalid fid");
+		return;
+	}
+
+	if (TYPE_OVERFLOW(off_t, off)) {
+		log_warnx("unexpected off_t size");
+		np_error(hdr->tag, "invalid offset");
+		return;
+	}
+
+	if (f->d == NULL) {
+		/* read a file */
+		howmuch = MIN(sizeof(buf), count);
+		r = pread(f->fd, buf, howmuch, (off_t)off);
+		if (r == -1)
+			np_errno(hdr->tag);
+		else
+			np_read(hdr->tag, r, buf);
+	} else {
+		if (off == 0 && f->offset != 0) {
+			rewinddir(f->d);
+			f->offset = 0;
+			evbuffer_drain(f->evb, EVBUFFER_LENGTH(f->evb));
+		}
+
+		if (off != f->offset) {
+			np_error(hdr->tag, "can't seek in directories");
+			return;
+		}
+
+		while (EVBUFFER_LENGTH(f->evb) < count) {
+			struct dirent *d;
+			struct stat sb;
+
+			if ((d = readdir(f->d)) == NULL)
+				break;
+			if (fstatat(f->fd, d->d_name, &sb, 0) == -1) {
+				warn("fstatat");
+				continue;
+			}
+			serialize_stat(d->d_name, &sb, f->evb);
+		}
+
+		count = MIN(count, EVBUFFER_LENGTH(f->evb));
+		np_read(hdr->tag, count, EVBUFFER_DATA(f->evb));
+		evbuffer_drain(f->evb, count);
+
+		f->offset += count;
+	}
+}
+
+static void
+twrite(struct np_msg_header *hdr, const uint8_t *data, size_t len)
+{
+	struct fid	*f;
+	ssize_t		 r;
+	uint64_t	 off;
+	uint32_t	 fid, count;
+
+	/* fid[4] offset[8] count[4] data[count] */
+	if (!NPREAD32("fid", &fid, &data, &len) ||
+	    !NPREAD64("off", &off, &data, &len) ||
+	    !NPREAD32("count", &count, &data, &len) ||
+	    len != count) {
+		client_send_listener(IMSG_CLOSE, NULL, 0);
+		client_shutdown();
+		return;
+	}
+
+	if ((f = fid_by_id(fid)) == NULL || f->fd == -1) {
+		np_error(hdr->tag, "invalid fid");
+		return;
+	}
+
+	if (!(f->iomode & O_WRONLY) &&
+	    !(f->iomode & O_RDWR)) {
+		np_error(hdr->tag, "fid not opened for writing");
+		return;
+	}
+
+	if (TYPE_OVERFLOW(off_t, off)) {
+		log_warnx("unexpected off_t size");
+		np_error(hdr->tag, "invalid offset");
+		return;
+	}
+
+	if ((r = pwrite(f->fd, data, len, off)) == -1)
+		np_errno(hdr->tag);
+	else
+		np_write(hdr->tag, r);
+}
+
+static void
+tstat(struct np_msg_header *hdr, const uint8_t *data, size_t len)
+{
+	struct evbuffer	*evb;
+	struct stat	 sb;
+	struct fid	*f;
+	int		 r;
+	uint32_t	 fid;
+
+	/* fid[4] */
+	if (!NPREAD32("fid", &fid, &data, &len)) {
+		client_send_listener(IMSG_CLOSE, NULL, 0);
+		client_shutdown();
+		return;
+	}
+
+	/*
+	 * plan9' stat(9P) is not clear on whether the stat is allowed
+	 * on opened fids or not.  We're allowing stat regardless of the
+	 * status of the fid.
+	 */
+
+	if ((f = fid_by_id(fid)) == NULL) {
+		np_error(hdr->tag, "invalid fid");
+		return;
+	}
+
+	if ((evb = evbuffer_new()) == NULL)
+		fatal("evbuffer_new");
+
+	if (f->fd != -1)
+		r = fstat(f->fd, &sb);
+	else if (f->qid.type & QTDIR)
+		r = fstat(f->dir->fd, &sb);
+	else
+		r = fstatat(f->dir->fd, f->fpath, &sb, 0);
+
+	if (r == -1) {
+		np_errno(hdr->tag);
+		evbuffer_free(evb);
+		return;
+	}
+
+	serialize_stat(f->fpath, &sb, evb);
+	np_stat(hdr->tag, EVBUFFER_LENGTH(evb), EVBUFFER_DATA(evb));
+	evbuffer_free(evb);
+}
+
+static void
+tremove(struct np_msg_header *hdr, const uint8_t *data, size_t len)
+{
+	struct fid	*f;
+	uint32_t	 fid;
+	int		 r;
+	char		 dirpath[PATH_MAX + 3];
+
+	/* fid[4] */
+	if (!NPREAD32("fid", &fid, &data, &len)) {
+		client_send_listener(IMSG_CLOSE, NULL, 0);
+		client_shutdown();
+		return;
+	}
+
+	if ((f = fid_by_id(fid)) == NULL) {
+		np_error(hdr->tag, "invalid fid");
+		return;
+	}
+
+	if (f->qid.type & QTDIR) { /* directory */
+		strlcpy(dirpath, "../", sizeof(dirpath));
+		strlcat(dirpath, f->fpath, sizeof(dirpath));
+		r = unlinkat(f->dir->fd, dirpath, AT_REMOVEDIR);
+	} else /* file */
+		r = unlinkat(f->dir->fd, f->fpath, 0);
+
+	if (r == -1)
+		np_errno(hdr->tag);
+	else
+		np_remove(hdr->tag);
+
+	free_fid(f);
+}
+
+static void
+handle_message(struct imsg *imsg, size_t len)
+{
+	struct msg {
+		uint8_t	 type;
+		void	(*fn)(struct np_msg_header *, const uint8_t *, size_t);
+	} msgs[] = {
+		{Tversion,	tversion},
+		{Tattach,	tattach},
+		{Tclunk,	tclunk},
+		{Tflush,	tflush},
+		{Twalk,		twalk},
+		{Topen,		topen},
+		{Tcreate,	tcreate},
+		{Tread,		tread},
+		{Twrite,	twrite},
+		{Tstat,		tstat},
+		{Tremove,	tremove},
+	};
+	struct np_msg_header	 hdr;
+	size_t			 i;
+	uint8_t			*data;
+
+#if DEBUG_PACKETS
+	hexdump("incoming packet", imsg->data, len);
+#endif
+
+	parse_message(imsg->data, len, &hdr, &data);
+	len -= HEADERSIZE;
+
+	log_debug("got request: len=%d type=%d[%s] tag=%d",
+	    hdr.len, hdr.type, pp_msg_type(hdr.type), hdr.tag);
+
+	if (!handshaked && hdr.type != Tversion) {
+		client_send_listener(IMSG_CLOSE, NULL, 0);
+		client_shutdown();
+		return;
+	}
+
+	for (i = 0; i < sizeof(msgs)/sizeof(msgs[0]); ++i) {
+		if (msgs[i].type != hdr.type)
+			continue;
+
+		msgs[i].fn(&hdr, data, len);
+		return;
+	}
+
+	np_error(hdr.tag, "Not supported.");
+}
blob - /dev/null
blob + 3797d2e8d3a63327a887ac03ec59c48ceb7f2e5e (mode 644)
--- /dev/null
+++ kamid/client.h
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+#ifndef CLIENT_H
+#define CLIENT_H
+
+__dead void	client(int, int);
+
+#endif
blob - /dev/null
blob + 5d8abc786358b8715dec9ab680a868c59bf80c0d (mode 644)
--- /dev/null
+++ kamid/control.c
@@ -0,0 +1,293 @@
+/*
+ * 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 <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/queue.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+
+#include <netinet/in.h>
+#include <net/if.h>
+
+#include <errno.h>
+#include <event.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <imsg.h>
+
+#include "control.h"
+#include "kamid.h"
+#include "listener.h"
+#include "log.h"
+#include "utils.h"
+
+#define	CONTROL_BACKLOG	5
+
+struct {
+	struct event	ev;
+	struct event	evt;
+	int		fd;
+} control_state = {.fd = -1};
+
+struct ctl_conn {
+	TAILQ_ENTRY(ctl_conn)	entry;
+	struct imsgev		iev;
+};
+
+TAILQ_HEAD(ctl_conns, ctl_conn)	ctl_conns = TAILQ_HEAD_INITIALIZER(ctl_conns);
+
+struct ctl_conn	*control_connbyfd(int);
+struct ctl_conn	*control_connbypid(pid_t);
+void		 control_close(int);
+
+int
+control_init(const char *path)
+{
+	struct sockaddr_un	 sun;
+	int			 fd;
+	mode_t			 old_umask;
+
+	if ((fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
+	    0)) == -1) {
+		log_warn("%s: socket", __func__);
+		return (-1);
+	}
+
+	memset(&sun, 0, sizeof(sun));
+	sun.sun_family = AF_UNIX;
+	strlcpy(sun.sun_path, path, sizeof(sun.sun_path));
+
+	if (unlink(path) == -1)
+		if (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", __func__, path);
+		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", __func__);
+		close(fd);
+		(void)unlink(path);
+		return (-1);
+	}
+
+	return (fd);
+}
+
+int
+control_listen(int fd)
+{
+	if (control_state.fd != -1)
+		fatalx("%s: received unexpected controlsock", __func__);
+
+	control_state.fd = fd;
+	if (listen(control_state.fd, CONTROL_BACKLOG) == -1) {
+		log_warn("%s: listen", __func__);
+		return (-1);
+	}
+
+	event_set(&control_state.ev, control_state.fd, EV_READ,
+	    control_accept, NULL);
+	event_add(&control_state.ev, NULL);
+	evtimer_set(&control_state.evt, control_accept, NULL);
+
+	return (0);
+}
+
+void
+control_accept(int listenfd, short event, void *bula)
+{
+	int			 connfd;
+	socklen_t		 len;
+	struct sockaddr_un	 sun;
+	struct ctl_conn		*c;
+
+	event_add(&control_state.ev, NULL);
+	if ((event & EV_TIMEOUT))
+		return;
+
+	len = sizeof(sun);
+	if ((connfd = accept4(listenfd, (struct sockaddr *)&sun, &len,
+	    SOCK_CLOEXEC | SOCK_NONBLOCK)) == -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(&control_state.ev);
+			evtimer_add(&control_state.evt, &evtpause);
+		} else if (errno != EWOULDBLOCK && errno != EINTR &&
+		    errno != ECONNABORTED)
+			log_warn("%s: accept4", __func__);
+		return;
+	}
+
+	if ((c = calloc(1, sizeof(struct ctl_conn))) == NULL) {
+		log_warn("%s: calloc", __func__);
+		close(connfd);
+		return;
+	}
+
+	imsg_init(&c->iev.ibuf, connfd);
+	c->iev.handler = control_dispatch_imsg;
+	c->iev.events = EV_READ;
+	event_set(&c->iev.ev, c->iev.ibuf.fd, c->iev.events,
+	    c->iev.handler, &c->iev);
+	event_add(&c->iev.ev, NULL);
+
+	TAILQ_INSERT_TAIL(&ctl_conns, c, entry);
+}
+
+struct ctl_conn *
+control_connbyfd(int fd)
+{
+	struct ctl_conn	*c;
+
+	TAILQ_FOREACH(c, &ctl_conns, entry) {
+		if (c->iev.ibuf.fd == fd)
+			break;
+	}
+
+	return (c);
+}
+
+struct ctl_conn *
+control_connbypid(pid_t pid)
+{
+	struct ctl_conn	*c;
+
+	TAILQ_FOREACH(c, &ctl_conns, entry) {
+		if (c->iev.ibuf.pid == pid)
+			break;
+	}
+
+	return (c);
+}
+
+void
+control_close(int fd)
+{
+	struct ctl_conn	*c;
+
+	if ((c = control_connbyfd(fd)) == NULL) {
+		log_warnx("%s: fd %d: not found", __func__, fd);
+		return;
+	}
+
+	msgbuf_clear(&c->iev.ibuf.w);
+	TAILQ_REMOVE(&ctl_conns, c, entry);
+
+	event_del(&c->iev.ev);
+	close(c->iev.ibuf.fd);
+
+	/* Some file descriptors are available again. */
+	if (evtimer_pending(&control_state.evt, NULL)) {
+		evtimer_del(&control_state.evt);
+		event_add(&control_state.ev, NULL);
+	}
+
+	free(c);
+}
+
+void
+control_dispatch_imsg(int fd, short event, void *bula)
+{
+	struct ctl_conn	*c;
+	struct imsg	 imsg;
+	ssize_t		 n;
+	int		 verbose;
+
+	if ((c = control_connbyfd(fd)) == NULL) {
+		log_warnx("%s: fd %d: not found", __func__, fd);
+		return;
+	}
+
+	if (event & EV_READ) {
+		if (((n = imsg_read(&c->iev.ibuf)) == -1 && errno != EAGAIN) ||
+		    n == 0) {
+			control_close(fd);
+			return;
+		}
+	}
+	if (event & EV_WRITE) {
+		if (msgbuf_write(&c->iev.ibuf.w) <= 0 && errno != EAGAIN) {
+			control_close(fd);
+			return;
+		}
+	}
+
+	for (;;) {
+		if ((n = imsg_get(&c->iev.ibuf, &imsg)) == -1) {
+			control_close(fd);
+			return;
+		}
+		if (n == 0)
+			break;
+
+		switch (imsg.hdr.type) {
+		case IMSG_CTL_RELOAD:
+			listener_imsg_compose_main(imsg.hdr.type, 0, NULL, 0);
+			break;
+		case IMSG_CTL_LOG_VERBOSE:
+			if (IMSG_DATA_SIZE(imsg) != sizeof(verbose))
+				break;
+
+			/* Forward to all other processes. */
+			listener_imsg_compose_main(imsg.hdr.type, imsg.hdr.pid,
+			    imsg.data, IMSG_DATA_SIZE(imsg));
+
+			/* XXX: send to every client? */
+
+			memcpy(&verbose, imsg.data, sizeof(verbose));
+			log_setverbose(verbose);
+			break;
+		default:
+			log_debug("%s: error handling imsg %d", __func__,
+			    imsg.hdr.type);
+			break;
+		}
+		imsg_free(&imsg);
+	}
+
+	imsg_event_add(&c->iev);
+}
+
+int
+control_imsg_relay(struct imsg *imsg)
+{
+	struct ctl_conn	*c;
+
+	if ((c = control_connbypid(imsg->hdr.pid)) == NULL)
+		return (0);
+
+	return (imsg_compose_event(&c->iev, imsg->hdr.type, 0, imsg->hdr.pid,
+	    -1, imsg->data, IMSG_DATA_SIZE(*imsg)));
+}
blob - /dev/null
blob + f9782b86db958a0bb3c26ecb88d581980ffd7dcd (mode 644)
--- /dev/null
+++ kamid/control.h
@@ -0,0 +1,26 @@
+/*
+ * 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 CONTROL_H
+#define CONTROL_H
+
+int	control_init(const char *);
+int	control_listen(int fd);
+void	control_accept(int, short, void *);
+void	control_dispatch_imsg(int, short, void *);
+int	control_imsg_relay(struct imsg *);
+
+#endif
blob - /dev/null
blob + e0f398aaf1fb243d4b11fbef4b3e08442fd9ea46 (mode 644)
--- /dev/null
+++ kamid/kamid.8
@@ -0,0 +1,81 @@
+.\" 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.
+.\"
+.Dd $Mdocdate: July 07 2021 $
+.Dt KAMID 8
+.Os
+.Sh NAME
+.Nm kamid
+.Nd 9p file server daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl dnv
+.Op Fl D Ar macro Ns = Ns Ar value
+.Op Fl f Pa file
+.Op Fl s Pa socket
+.Sh DESCRIPTION
+.Nm
+is a 9p file server daemon.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl D Ar macro Ns = Ns Ar value
+Set a
+.Ar macro
+to a
+.Ar value .
+Macros can be referenced in the configuration files.
+.It Fl d
+Do not daemonize.
+If this option is specified,
+.Nm
+will run in the foreground and log to
+.Em stderr .
+.It Fl f Ar file
+specify an alternative configuration file.
+.It Fl n
+Configtest mode.
+Only check the configuration file for validity.
+.It Fl s Ar socket
+Use an alternate location for the default control socket.
+.It Fl v
+Produce more verbose output.
+.El
+.Sh FILES
+.Bl -tag -width "/var/run/kamid.sockXX" -compact
+.It Pa /etc/kamid.conf
+Default
+.Nm
+configuration file.
+.It Pa /var/run/kamid.sock
+UNIX-domain socket used for communication with
+.Xr kamictl 8 .
+.El
+.Sh SEE ALSO
+.Xr kami.conf 5 ,
+.Xr kamictl 8
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+program was written by
+.An Omar Polo Aq Mt op@omarpolo.com .
+.Sh CAVEATS
+.Nm
+doesn't handle very well when a user directory is over multiple
+devices since it uses the inode of files to build the QID path field.
+.Pp
+Opening/creating a file with the
+.Dv OEXEC
+result in an error.
blob - /dev/null
blob + cd27289756b18d4d7bceb853c1b5d5f210686808 (mode 644)
--- /dev/null
+++ kamid/kamid.c
@@ -0,0 +1,632 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ * Copyright (c) 2018 Florian Obser <florian@openbsd.org>
+ * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org>
+ * Copyright (c) 2004 Esben Norby <norby@openbsd.org>
+ * 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 <sys/socket.h>
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/uio.h>
+#include <sys/wait.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <imsg.h>
+
+#include "client.h"
+#include "control.h"
+#include "kamid.h"
+#include "listener.h"
+#include "log.h"
+#include "sandbox.h"
+#include "table.h"
+#include "utils.h"
+
+enum kd_process {
+	PROC_MAIN,
+	PROC_LISTENER,
+	PROC_CLIENTCONN,
+};
+
+const char	*saved_argv0;
+static int	 debug, nflag;
+int		 verbose;
+
+__dead void	usage(void);
+
+void		main_sig_handler(int, short, void *);
+void		main_dispatch_listener(int, short, void *);
+int		main_reload(void);
+int		main_imsg_send_config(struct kd_conf *);
+void		main_dispatch_listener(int, short, void *);
+__dead void	main_shutdown(void);
+
+static pid_t	start_child(enum kd_process, int, int, int);
+
+struct kd_conf		*main_conf;
+static struct imsgev	*iev_listener;
+const char		*conffile;
+pid_t			 listener_pid;
+uint32_t		 cmd_opts;
+
+__dead void
+usage(void)
+{
+	fprintf(stderr, "usage: %s [-dnv] [-f file] [-s socket]\n",
+	    getprogname());
+	exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+	struct event	 ev_sigint, ev_sigterm, ev_sighup;
+	int		 ch;
+	int		 listener_flag = 0, client_flag = 0;
+	int		 pipe_main2listener[2];
+	int		 control_fd;
+	const char	*csock;
+
+	conffile = KD_CONF_FILE;
+	csock = KD_SOCKET;
+
+	log_init(1, LOG_DAEMON);	/* Log to stderr until deamonized. */
+	log_setverbose(1);
+
+	saved_argv0 = argv[0];
+	if (saved_argv0 == NULL)
+		saved_argv0 = "kamid";
+
+	while ((ch = getopt(argc, argv, "D:df:nsT:v")) != -1) {
+		switch (ch) {
+		case 'D':
+			if (cmdline_symset(optarg) == -1)
+                                log_warnx("could not parse macro definition %s",
+					optarg);
+			break;
+		case 'd':
+			debug = 1;
+			break;
+		case 'f':
+			conffile = optarg;
+			break;
+		case 'n':
+			nflag = 1;
+			break;
+		case 's':
+			csock = optarg;
+			break;
+		case 'T':
+			switch (*optarg) {
+			case 'c':
+				client_flag = 1;
+				break;
+			case 'l':
+				listener_flag = 1;
+				break;
+			default:
+				fatalx("invalid process spec %c", *optarg);
+			}
+			break;
+		case 'v':
+			verbose = 1;
+			break;
+		default:
+			usage();
+		}
+	}
+
+	argc -= optind;
+	argv += optind;
+	if (argc > 0 || (listener_flag && client_flag))
+		usage();
+
+	if (client_flag)
+		client(debug, verbose);
+	else if (listener_flag)
+		listener(debug, verbose);
+
+	if ((main_conf = parse_config(conffile)) == NULL)
+		exit(1);
+
+	if (nflag) {
+		fprintf(stderr, "configuration OK\n");
+		exit(0);
+	}
+
+	/* Check for root privileges. */
+        if (geteuid())
+                fatalx("need root privileges");
+
+	/* Check for assigned daemon user. */
+	if (getpwnam(KD_USER) == NULL)
+		fatalx("unknown user %s", KD_USER);
+
+	log_init(debug, LOG_DAEMON);
+	log_setverbose(verbose);
+
+	if (!debug)
+		daemon(1, 0);
+
+	log_info("startup");
+
+	if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
+	    PF_UNSPEC, pipe_main2listener) == -1)
+		fatal("main2listener socketpair");
+
+	/* Start children. */
+	listener_pid = start_child(PROC_LISTENER, pipe_main2listener[1],
+	    debug, verbose);
+
+	log_procinit("main");
+
+	event_init();
+
+	/* Setup signal handler */
+	signal_set(&ev_sigint, SIGINT, main_sig_handler, NULL);
+	signal_set(&ev_sigterm, SIGTERM, main_sig_handler, NULL);
+	signal_set(&ev_sighup, SIGHUP, main_sig_handler, NULL);
+
+	signal_add(&ev_sigint, NULL);
+	signal_add(&ev_sigterm, NULL);
+	signal_add(&ev_sighup, NULL);
+
+	signal(SIGCHLD, SIG_IGN);
+	signal(SIGPIPE, SIG_IGN);
+
+	if ((iev_listener = malloc(sizeof(*iev_listener))) == NULL)
+		fatal(NULL);
+	imsg_init(&iev_listener->ibuf, pipe_main2listener[0]);
+	iev_listener->handler = main_dispatch_listener;
+
+	/* Setup event handlers for pipes to listener. */
+	iev_listener->events = EV_READ;
+	event_set(&iev_listener->ev, iev_listener->ibuf.fd,
+	    iev_listener->events, iev_listener->handler, iev_listener);
+	event_add(&iev_listener->ev, NULL);
+
+	if ((control_fd = control_init(csock)) == -1)
+		fatalx("control socket setup failed");
+
+	main_imsg_compose_listener(IMSG_CONTROLFD, control_fd, 0,
+	    NULL, 0);
+	main_imsg_send_config(main_conf);
+
+	sandbox_main();
+
+	event_dispatch();
+
+	main_shutdown();
+	return 0;
+}
+
+void
+main_sig_handler(int sig, short event, void *arg)
+{
+	/*
+	 * Normal signal handler rules don't apply because libevent
+	 * decouples for us.
+	 */
+
+	switch (sig) {
+	case SIGTERM:
+	case SIGINT:
+		main_shutdown();
+		break;
+	case SIGHUP:
+		if (main_reload() == -1)
+			log_warnx("configuration reload failed");
+		else
+			log_debug("configuration reloaded");
+		break;
+	default:
+		fatalx("unexpected signal %d", sig);
+	}
+}
+
+static inline struct table *
+auth_table_by_id(uint32_t id)
+{
+	struct kd_listen_conf *listen;
+
+	STAILQ_FOREACH(listen, &main_conf->listen_head, entry) {
+		if (listen->id == id)
+			return listen->auth_table;
+	}
+
+	return NULL;
+}
+
+static inline struct table *
+virtual_table_by_id(uint32_t id)
+{
+	struct kd_listen_conf *listen;
+
+	STAILQ_FOREACH(listen, &main_conf->listen_head, entry) {
+		if (listen->id == id)
+			return listen->virtual_table;
+	}
+
+	return NULL;
+}
+
+static inline struct table *
+userdata_table_by_id(uint32_t id)
+{
+	struct kd_listen_conf *listen;
+
+	STAILQ_FOREACH(listen, &main_conf->listen_head, entry) {
+		if (listen->id == id)
+			return listen->userdata_table;
+	}
+
+	return NULL;
+}
+
+static inline void
+do_auth_tls(struct imsg *imsg)
+{
+	char *username = NULL, *user = NULL, *home = NULL, *local_user;
+	struct passwd *pw;
+	struct table *auth, *virt, *userdata;
+	struct kd_auth_req kauth;
+	int p[2], free_home = 1;
+
+	if (sizeof(kauth) != IMSG_DATA_SIZE(*imsg))
+		fatal("wrong size for IMSG_AUTH_TLS: "
+		    "got %lu; want %lu", IMSG_DATA_SIZE(*imsg),
+		    sizeof(kauth));
+	memcpy(&kauth, imsg->data, sizeof(kauth));
+
+	if (memmem(kauth.hash, sizeof(kauth.hash), "", 1) == NULL)
+                fatal("non NUL-terminated hash received");
+
+	log_debug("tls id=%u hash=%s", kauth.listen_id, kauth.hash);
+
+	if ((auth = auth_table_by_id(kauth.listen_id)) == NULL)
+		fatal("request for invalid listener id %d", imsg->hdr.pid);
+
+	virt = virtual_table_by_id(kauth.listen_id);
+	userdata = userdata_table_by_id(kauth.listen_id);
+
+	if (table_lookup(auth, kauth.hash, &username) == -1) {
+		log_warnx("login failed for hash %s", kauth.hash);
+		goto err;
+	}
+
+	if (virt != NULL && table_lookup(virt, username, &user) == -1) {
+		log_warnx("virtual lookup failed for user %s", username);
+		goto err;
+	}
+
+	/* the local user */
+	local_user = user != NULL ? user : username;
+
+	if (user != NULL)
+		log_debug("virtual user %s matched local user %s",
+		    username, user);
+	else
+		log_debug("matched local user %s", username);
+
+	if (userdata != NULL && table_lookup(userdata, username, &home)
+	    == -1) {
+		log_warnx("userdata lookup failed for user %s", username);
+		goto err;
+	} else if (userdata == NULL) {
+		if ((pw = getpwnam(local_user)) == NULL) {
+			log_warnx("getpwnam(%s) failed", local_user);
+			goto err;
+		}
+
+		free_home = 0;
+		home = pw->pw_dir;
+	}
+
+	if (user != NULL)
+		log_debug("matched home %s for virtual user %s",
+		    home, username);
+	else
+		log_debug("matched home %s for local user %s",
+		    home, username);
+
+	if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK,
+	    PF_UNSPEC, p) == -1)
+		fatal("socketpair");
+
+	start_child(PROC_CLIENTCONN, p[1], debug, verbose);
+
+	main_imsg_compose_listener(IMSG_AUTH, p[0], imsg->hdr.peerid,
+	    local_user, strlen(local_user)+1);
+	main_imsg_compose_listener(IMSG_AUTH_DIR, -1, imsg->hdr.peerid,
+	    home, strlen(home)+1);
+
+	free(username);
+	free(user);
+	if (free_home)
+		free(home);
+	return;
+
+err:
+	free(username);
+	free(user);
+	if (free_home)
+		free(home);
+	main_imsg_compose_listener(IMSG_AUTH, -1, imsg->hdr.peerid,
+	    NULL, 0);
+}
+
+void
+main_dispatch_listener(int fd, short event, void *d)
+{
+	struct imsgev	*iev = d;
+	struct imsgbuf	*ibuf;
+	struct imsg	 imsg;
+	ssize_t		 n;
+	int		 shut = 0;
+
+	ibuf = &iev->ibuf;
+
+	if (event & EV_READ) {
+		if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+			fatal("imsg_read error");
+		if (n == 0)	/* Connection closed. */
+			shut = 1;
+	}
+	if (event & EV_WRITE) {
+		if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+			fatal("msgbuf_write");
+		if (n == 0)	/* Connection closed. */
+			shut = 1;
+	}
+
+	for (;;) {
+		if ((n = imsg_get(ibuf, &imsg)) == -1)
+			fatal("imsg_get");
+		if (n == 0)	/* No more messages. */
+			break;
+
+		switch (imsg.hdr.type) {
+		case IMSG_AUTH_TLS:
+			do_auth_tls(&imsg);
+			break;
+		default:
+			log_debug("%s: error handling imsg %d", __func__,
+				imsg.hdr.type);
+			break;
+		}
+		imsg_free(&imsg);
+	}
+	if (!shut)
+		imsg_event_add(iev);
+	else {
+		/* This pipe is dead.  Remove its event handler. */
+		event_del(&iev->ev);
+		event_loopexit(NULL);
+	}
+}
+
+int
+main_reload(void)
+{
+	struct kd_conf *xconf;
+
+	if ((xconf = parse_config(conffile)) == NULL)
+		return -1;
+
+	if (main_imsg_send_config(xconf) == -1)
+		return -1;
+
+	merge_config(main_conf, xconf);
+
+	return 0;
+}
+
+static inline int
+make_socket_for(struct kd_listen_conf *l)
+{
+	struct sockaddr_in	addr4;
+	size_t			len;
+	int			fd, v;
+
+	memset(&addr4, 0, sizeof(addr4));
+	addr4.sin_family = AF_INET;
+	addr4.sin_port = htons(l->port);
+	addr4.sin_addr.s_addr = INADDR_ANY;
+
+	if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
+		fatal("socket");
+
+	v = 1;
+	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &v, sizeof(v)) == -1)
+		fatal("setsockopt(SO_REUSEADDR)");
+
+	v = 1;
+	if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &v, sizeof(v)) == -1)
+		fatal("setsockopt(SO_REUSEPORT)");
+
+	len = sizeof(addr4);
+	if (bind(fd, (struct sockaddr *)&addr4, len) == -1)
+		fatal("bind(%s, %d)", l->iface, l->port);
+
+	if (listen(fd, 16) == -1)
+		fatal("l(%s, %d)", l->iface, l->port);
+
+	return fd;
+}
+
+int
+main_imsg_send_config(struct kd_conf *xconf)
+{
+	struct kd_pki_conf *pki;
+	struct kd_listen_conf *listen;
+
+#define SEND(type, fd, data, len) do {					\
+		if (main_imsg_compose_listener(type, fd, 0, data, len)	\
+		    == -1)						\
+			return -1;					\
+	} while (0)
+
+	/* Send fixed part of config to children. */
+	SEND(IMSG_RECONF_CONF, -1, xconf, sizeof(*xconf));
+
+	STAILQ_FOREACH(pki, &xconf->pki_head, entry) {
+		log_debug("sending pki %s", pki->name);
+		SEND(IMSG_RECONF_PKI, -1, pki->name, sizeof(pki->name));
+		SEND(IMSG_RECONF_PKI_CERT, -1, pki->cert, pki->certlen);
+		SEND(IMSG_RECONF_PKI_KEY, -1, pki->key, pki->keylen);
+	}
+
+	STAILQ_FOREACH(listen, &xconf->listen_head, entry) {
+		log_debug("sending listen on port %d", listen->port);
+		SEND(IMSG_RECONF_LISTEN, make_socket_for(listen), listen,
+		    sizeof(*listen));
+	}
+
+	SEND(IMSG_RECONF_END, -1, NULL, 0);
+	return 0;
+
+#undef SEND
+}
+
+void
+merge_config(struct kd_conf *conf, struct kd_conf *xconf)
+{
+	/* do stuff... */
+
+	free(xconf);
+}
+
+struct kd_conf *
+config_new_empty(void)
+{
+	struct kd_conf *xconf;
+
+	if ((xconf = calloc(1, sizeof(*xconf))) == NULL)
+		fatal(NULL);
+
+	/* set default values */
+
+	return xconf;
+}
+
+void
+config_clear(struct kd_conf *conf)
+{
+	struct kd_conf *xconf;
+
+	/* Merge current config with an empty one. */
+	xconf = config_new_empty();
+	merge_config(conf, xconf);
+
+	free(conf);
+}
+
+__dead void
+main_shutdown(void)
+{
+	pid_t	pid;
+	int	status;
+
+	/* close pipes. */
+        config_clear(main_conf);
+
+	log_debug("waiting for children to terminate");
+	do {
+		pid = wait(&status);
+		if (pid == -1) {
+			if (errno != EINTR && errno != ECHILD)
+				fatal("wait");
+		} else if (WIFSIGNALED(status))
+			log_warnx("%s terminated; signal %d",
+			    (pid == listener_pid) ? "logger" : "clientconn",
+			    WTERMSIG(status));
+	} while (pid != -1 || (pid == -1 && errno == EINTR));
+
+	free(iev_listener);
+
+	log_info("terminating");
+	exit(0);
+}
+
+static pid_t
+start_child(enum kd_process p, int fd, int debug, int verbose)
+{
+	const char	*argv[5];
+	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(F_SETFD, 0) == -1)
+		fatal("cannot setup imsg fd");
+
+	argv[argc++] = saved_argv0;
+	switch (p) {
+	case PROC_MAIN:
+		fatalx("Can not start main process");
+	case PROC_LISTENER:
+		argv[argc++] = "-Tl";
+		break;
+	case PROC_CLIENTCONN:
+		argv[argc++] = "-Tc";
+		break;
+	}
+	if (debug)
+		argv[argc++] = "-d";
+	if (verbose)
+		argv[argc++] = "-v";
+	argv[argc++] = NULL;
+
+	/* really? */
+	execvp(saved_argv0, (char *const *)argv);
+	fatal("execvp");
+}
+
+int
+main_imsg_compose_listener(int type, int fd, uint32_t peerid,
+    const void *data, uint16_t datalen)
+{
+	if (iev_listener)
+		return imsg_compose_event(iev_listener, type, peerid, 0,
+		    fd, data, datalen);
+	else
+		return -1;
+}
blob - /dev/null
blob + fd14ab7c7c727a8d7d30270434c294902e08c6e2 (mode 644)
--- /dev/null
+++ kamid/kamid.conf.5
@@ -0,0 +1,131 @@
+.\" 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.
+.\"
+.Dd $Mdocdate: December 14 2021 $
+.Dt KAMID.CONF 5
+.Os
+.Sh NAME
+.Nm kamid.conf
+.Nd 9p file server daemon configuration file
+.Sh DESCRIPTION
+.Nm
+is the configuration file for the 9p file server daemon
+.Xr kamid 8 .
+.Pp
+The format of the configuration file is fairly flexible.
+The current line can be extended over multiple lines using a backslash
+.Pq Sq \e .
+Comments can be put anywhere in the file using a hash mark
+.Pq Sq # ,
+and extend to the end of the current line.
+Care should be taken when commenting out multi-line text: the comment is
+effective until the end of the entire block.
+Arguments names not beginning with a letter, digit, or underscore, as
+well as reserved words
+(such as
+.Ic listen ,
+.Ic pki
+and
+.Ic table )
+must be quoted.
+Arguments containing whitespace should be surrounded by double quotes
+.Pq \&" .
+.Pp
+Macros can be defined that are later expanded in context.
+Macro names must start with a letter, digit, or underscore, and may
+contain any of those characters, but may not be reserved words.
+Macros are not expanded inside quotes.
+For example:
+.Bd -literal -offset indent
+lan_addr = "192.168.0.1"
+listen on $lan_addr
+listen on $lan_addr tls auth <users>
+.Ed
+.Pp
+Additional configuration files can be included with the
+.Ic include
+keyword, for example:
+.Bd -literal -offset indent
+include "/etc/kamid.conf.local"
+.Ed
+.Pp
+The syntax of
+.Nm
+is described below.
+.Bl -tag -width Ds
+.It Ic listen Op Ar options...
+The options are as follows:
+.Bl -tag -width Ds
+.It Ic on Ar address Ic port Ar number
+Listen on the
+.Ar address
+for incoming connection on the given port
+.Ar number .
+.Ar address
+can be an IP address or a domain name.
+.It Ic tls Ic pki Ar name
+Use the tls certificate
+.Ar name
+previously defined with the
+.Ic pki
+rule.
+.It Ic auth Pf < Ar table Ns >
+Use the given authentication
+.Ar table
+to authorize the clients.
+.It Ic userdata Pf < Ar table Ns >
+Maps user
+.Pq virtuals or not
+to their exported tree.
+By default the user home directory obtained with
+.Xr getpwnam 3
+is used.
+.It Ic virtual Pf < Ar table Ns >
+Maps virtual users to local user.
+.El
+.It Ic pki Ar pkiname Ic cert Ar certfile
+Associate certificate file
+.Ar certfile
+with pki entry
+.Ar pkiname .
+The pki entry defines a keypair configuration that can be referenced in
+listener rules.
+.It Ic pki Ar pkiname Ic key Ar keyfile
+Associate the key located in
+.Ar keyfile
+with pki entry
+.Ar pkiname .
+.\" TODO: document the other syntax for the table
+.It Ic table Ar name Brq Ar value Cm => Ar value Oo , Ar ... Oc
+Tables provide additional configuration information for
+.Xr kamid 8
+in the form of key-value mappings.
+.Pp
+Declare a mapping table containing the given static
+.Ar key Ns Pf - Ar value
+pairs.
+.El
+.Sh EXAMPLES
+A sample configuration file:
+.Bd -literal -offset indent
+pki localhost cert "/etc/ssl/localhost.crt"
+pki localhost key "/etc/ssl/private/localhost.key"
+
+table users { "SHA256:..." => "op" }
+
+listen on localhost port 1337 tls pki localhost auth <users>
+.Ed
+.Sh SEE ALSO
+.Xr kamictl 8 ,
+.Xr kamid 8
blob - /dev/null
blob + db48d7e0a64590177becf74fe80661d26f1c0d01 (mode 644)
--- /dev/null
+++ kamid/kamid.h
@@ -0,0 +1,143 @@
+/*
+ * 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.
+ */
+
+#ifndef KAMID_H
+#define KAMID_H
+
+#include <limits.h>
+#include <stdint.h>
+#include <tls.h>
+
+/* TODO: make these customizable */
+#define KD_CONF_FILE		"/etc/kamid.conf"
+#define KD_USER			"_kamid"
+#define KD_SOCKET		"/var/run/kamid.sock"
+
+#define IMSG_DATA_SIZE(imsg)	((imsg).hdr.len - IMSG_HEADER_SIZE)
+
+enum imsg_type {
+	IMSG_NONE,
+	IMSG_CTL_LOG_VERBOSE,
+	IMSG_CTL_RELOAD,
+	IMSG_CONTROLFD,
+	IMSG_STARTUP,
+	IMSG_RECONF_CONF,
+	IMSG_RECONF_PKI,
+	IMSG_RECONF_PKI_CERT,
+	IMSG_RECONF_PKI_KEY,
+	IMSG_RECONF_LISTEN,
+	IMSG_RECONF_END,
+	IMSG_AUTH,
+	IMSG_AUTH_DIR,
+	IMSG_AUTH_TLS,
+	IMSG_CONN_GONE,
+	IMSG_BUF,
+	IMSG_MSIZE,
+	IMSG_CLOSE,
+};
+
+struct kd_options_conf {
+	/* ... */
+};
+
+enum table_type {
+	T_NONE		= 0,
+	T_HASH		= 0x01,
+};
+
+struct table {
+	char			 t_name[LINE_MAX];
+	enum table_type		 t_type;
+	char			 t_path[PATH_MAX];
+	void			*t_handle;
+	struct table_backend	*t_backend;
+};
+
+struct table_backend {
+	const char	*name;
+	int		(*open)(struct table *);
+	int		(*add)(struct table *, const char *, const char *);
+	int		(*lookup)(struct table *, const char *, char **);
+	void		(*close)(struct table *);
+};
+
+/* table_static.c */
+extern struct table_backend table_static;
+
+#define L_NONE	0x0
+#define L_TLS	0x1
+struct kd_listen_conf {
+	STAILQ_ENTRY(kd_listen_conf)	 entry;
+	uint32_t			 id;
+	uint32_t			 flags;
+	int				 fd;
+	char				 iface[LINE_MAX];
+	uint16_t			 port;
+
+	/* certificate hash => (virtual) user */
+	struct table			*auth_table;
+
+	/* virtual user => local user */
+	struct table			*virtual_table;
+
+	/* (virtual) user => export directory */
+	struct table			*userdata_table;
+
+	char				 pki[LINE_MAX];
+	struct event			 ev;
+	struct tls			*ctx;
+};
+
+struct kd_pki_conf {
+	STAILQ_ENTRY(kd_pki_conf)	 entry;
+	char				 name[LINE_MAX];
+	uint8_t				*cert;
+	size_t				 certlen;
+	uint8_t				*key;
+	size_t				 keylen;
+	struct tls_config		*tlsconf;
+};
+
+struct kd_tables_conf {
+	STAILQ_ENTRY(kd_tables_conf)	 entry;
+	struct table			*table;
+};
+
+struct kd_conf {
+	struct kd_options_conf					 kd_options;
+	STAILQ_HEAD(kd_pki_conf_head, kd_pki_conf)		 pki_head;
+	STAILQ_HEAD(kd_tables_conf_head, kd_tables_conf)	 table_head;
+	STAILQ_HEAD(kd_listen_conf_head, kd_listen_conf)	 listen_head;
+};
+
+struct kd_auth_req {
+	uint32_t	listen_id;
+	char		hash[128+1];
+};
+
+/* kamid.c */
+extern int verbose;
+int	main_imsg_compose_listener(int, int, uint32_t, const void *, uint16_t);
+void	merge_config(struct kd_conf *, struct kd_conf *);
+
+struct kd_conf	*config_new_empty(void);
+void		 config_clear(struct kd_conf *);
+
+/* parse.y */
+struct kd_conf	*parse_config(const char *);
+int		 cmdline_symset(char *);
+
+#endif
blob - /dev/null
blob + 655c05f33499a226890eee263131d3efa95be0b9 (mode 644)
--- /dev/null
+++ kamid/listener.c
@@ -0,0 +1,939 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ * Copyright (c) 2018 Florian Obser <florian@openbsd.org>
+ * Copyright (c) 2004, 2005 Claudio Jeker <claudio@openbsd.org>
+ * Copyright (c) 2004 Esben Norby <norby@openbsd.org>
+ * 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 <sys/socket.h>
+#include <sys/types.h>
+#include <sys/tree.h>
+#include <sys/queue.h>
+#include <sys/uio.h>
+
+#include <endian.h>
+#include <errno.h>
+#include <event.h>
+#include <inttypes.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <imsg.h>
+
+#include "control.h"
+#include "kami.h"
+#include "kamid.h"
+#include "listener.h"
+#include "log.h"
+#include "sandbox.h"
+#include "utils.h"
+
+static struct kd_conf	*listener_conf;
+static struct imsgev	*iev_main;
+
+static void	listener_sig_handler(int, short, void *);
+__dead void	listener_shutdown(void);
+
+SPLAY_HEAD(clients_tree_id, client) clients;
+
+struct client {
+	uint32_t		 id;
+	uint32_t		 lid;
+	uint32_t		 msize;
+	int			 fd;
+	int			 done;
+	struct tls		*ctx;
+	struct event		 event;
+	struct imsgev		 iev;
+	struct bufferevent	*bev;
+	SPLAY_ENTRY(client)	 sp_entry;
+};
+
+static void	listener_imsg_event_add(struct imsgev *, void *);
+static void	listener_dispatch_client(int, short, void *);
+static int	listener_imsg_compose_client(struct client *, int,
+    uint32_t, const void *, uint16_t);
+
+static void	apply_config(struct kd_conf *);
+static void	handle_accept(int, short, void *);
+
+static void	handle_handshake(int, short, void *);
+static void	client_read(struct bufferevent *, void *);
+static void	client_write(struct bufferevent *, void *);
+static void	client_error(struct bufferevent *, short, void *);
+static void	client_tls_readcb(int, short, void *);
+static void	client_tls_writecb(int, short, void *);
+static void	close_conn(struct client *);
+static void	handle_close(int, short, void *);
+
+static inline int
+clients_tree_cmp(struct client *a, struct client *b)
+{
+	if (a->id == b->id)
+		return 0;
+	else if (a->id < b->id)
+		return -1;
+	else
+		return +1;
+}
+
+SPLAY_PROTOTYPE(clients_tree_id, client, sp_entry, clients_tree_cmp);
+SPLAY_GENERATE(clients_tree_id, client, sp_entry, clients_tree_cmp)
+
+static void
+listener_sig_handler(int sig, short event, void *d)
+{
+	/*
+	 * Normal signal handler rules don't apply because libevent
+	 * decouples for us.
+	 */
+
+	switch (sig) {
+	case SIGINT:
+	case SIGTERM:
+		listener_shutdown();
+	default:
+		fatalx("unexpected signal %d", sig);
+	}
+}
+
+void
+listener(int debug, int verbose)
+{
+	struct event		 ev_sigint, ev_sigterm;
+	struct passwd		*pw;
+
+	/* listener_conf = config_new_empty(); */
+
+	log_init(debug, LOG_DAEMON);
+	log_setverbose(verbose);
+
+	if ((pw = getpwnam(KD_USER)) == NULL)
+		fatal("getpwnam");
+
+	if (chroot(pw->pw_dir) == -1)
+		fatal("chroot");
+	if (chdir("/") == -1)
+		fatal("chdir(\"/\")");
+
+	setproctitle("listener");
+	log_procinit("listener");
+
+	if (setgroups(1, &pw->pw_gid) ||
+	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
+	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
+		fatal("can't drop privileges");
+
+	event_init();
+
+	/* Setup signal handlers(s). */
+	signal_set(&ev_sigint, SIGINT, listener_sig_handler, NULL);
+	signal_set(&ev_sigterm, SIGTERM, listener_sig_handler, NULL);
+
+	signal_add(&ev_sigint, NULL);
+	signal_add(&ev_sigterm, NULL);
+
+	signal(SIGPIPE, SIG_IGN);
+	signal(SIGHUP, SIG_IGN);
+
+	/* Setup pipe and event handler to the main process. */
+	if ((iev_main = malloc(sizeof(*iev_main))) == NULL)
+		fatal(NULL);
+
+	imsg_init(&iev_main->ibuf, 3);
+	iev_main->handler = listener_dispatch_main;
+
+	/* Setup event handlers. */
+	iev_main->events = EV_READ;
+	event_set(&iev_main->ev, iev_main->ibuf.fd, iev_main->events,
+	    iev_main->handler, iev_main);
+	event_add(&iev_main->ev, NULL);
+
+	sandbox_listener();
+	event_dispatch();
+	listener_shutdown();
+}
+
+__dead void
+listener_shutdown(void)
+{
+	msgbuf_clear(&iev_main->ibuf.w);
+	close(iev_main->ibuf.fd);
+
+	config_clear(listener_conf);
+
+	free(iev_main);
+
+	log_info("listener exiting");
+	exit(0);
+}
+
+static void
+listener_receive_config(struct imsg *imsg, struct kd_conf **nconf,
+    struct kd_pki_conf **pki)
+{
+	struct kd_listen_conf *listen;
+	char *t;
+
+	switch (imsg->hdr.type) {
+	case IMSG_RECONF_CONF:
+		if (*nconf != NULL)
+			fatalx("%s: IMSG_RECONF_CONF already in "
+			    "progress", __func__);
+
+		if (listener_conf != NULL)
+			fatalx("%s: don't know how reload the "
+			    "configuration yet", __func__);
+
+		if (IMSG_DATA_SIZE(*imsg) != sizeof(struct kd_conf))
+			fatalx("%s: IMSG_RECONF_CONF wrong length: %lu",
+			    __func__, IMSG_DATA_SIZE(*imsg));
+		if ((*nconf = malloc(sizeof(**nconf))) == NULL)
+			fatal(NULL);
+		memcpy(*nconf, imsg->data, sizeof(**nconf));
+		memset(&(*nconf)->pki_head, 0, sizeof((*nconf)->pki_head));
+		memset(&(*nconf)->table_head, 0, sizeof((*nconf)->table_head));
+		memset(&(*nconf)->listen_head, 0, sizeof((*nconf)->listen_head));
+		break;
+	case IMSG_RECONF_PKI:
+		if (*nconf == NULL)
+			fatalx("%s: IMSG_RECONF_PKI without "
+			    "IMSG_RECONF_CONF", __func__);
+		*pki = xcalloc(1, sizeof(**pki));
+		t = imsg->data;
+		t[IMSG_DATA_SIZE(*imsg)-1] = '\0';
+		strlcpy((*pki)->name, t, sizeof((*pki)->name));
+		break;
+	case IMSG_RECONF_PKI_CERT:
+		if (*pki == NULL)
+			fatalx("%s: IMSG_RECONF_PKI_CERT without "
+			    "IMSG_RECONF_PKI", __func__);
+		(*pki)->certlen = IMSG_DATA_SIZE(*imsg);
+		(*pki)->cert = xmemdup(imsg->data, (*pki)->certlen);
+		break;
+	case IMSG_RECONF_PKI_KEY:
+		if (*pki == NULL)
+			fatalx("%s: IMSG_RECONF_PKI_KEY without "
+			    "IMSG_RECONF_PKI", __func__);
+		(*pki)->keylen = IMSG_DATA_SIZE(*imsg);
+		(*pki)->key = xmemdup(imsg->data, (*pki)->keylen);
+		STAILQ_INSERT_HEAD(&(*nconf)->pki_head, *pki, entry);
+		pki = NULL;
+		break;
+	case IMSG_RECONF_LISTEN:
+		if (*nconf == NULL)
+			fatalx("%s: IMSG_RECONF_LISTEN without "
+			    "IMSG_RECONF_CONF", __func__);
+		if (IMSG_DATA_SIZE(*imsg) != sizeof(*listen))
+			fatalx("%s: IMSG_RECONF_LISTEN wrong length: %lu",
+			    __func__, IMSG_DATA_SIZE(*imsg));
+		listen = xcalloc(1, sizeof(*listen));
+		memcpy(listen, imsg->data, sizeof(*listen));
+		memset(&listen->entry, 0, sizeof(listen->entry));
+		if ((listen->fd = imsg->fd) == -1)
+			fatalx("%s: IMSG_RECONF_LISTEN no fd",
+			    __func__);
+		listen->auth_table = NULL;
+		memset(&listen->ev, 0, sizeof(listen->ev));
+		STAILQ_INSERT_HEAD(&(*nconf)->listen_head, listen, entry);
+		break;
+	case IMSG_RECONF_END:
+		if (*nconf == NULL)
+			fatalx("%s: IMSG_RECONF_END without "
+			    "IMSG_RECONF_CONF", __func__);
+		/* merge_config(listener_conf, nconf); */
+		apply_config(*nconf);
+		*nconf = NULL;
+		break;
+	}
+}
+
+static inline struct kd_listen_conf *
+listen_by_id(uint32_t id)
+{
+	struct kd_listen_conf *l;
+
+	STAILQ_FOREACH(l, &listener_conf->listen_head, entry) {
+		if (l->id == id)
+			return l;
+	}
+	return NULL;
+}
+
+void
+listener_dispatch_main(int fd, short event, void *d)
+{
+	static struct kd_conf		*nconf;
+	static struct kd_pki_conf	*pki;
+	struct kd_listen_conf		*listen;
+	struct client			*client, find;
+	struct imsg			 imsg;
+	struct imsgev			*iev = d;
+	struct imsgbuf			*ibuf;
+	ssize_t				 n;
+	int				 shut = 0;
+
+	ibuf = &iev->ibuf;
+
+	if (event & EV_READ) {
+		if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+			fatal("imsg_read error");
+		if (n == 0)	/* Connection closed. */
+			shut = 1;
+	}
+	if (event & EV_WRITE) {
+		if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+			fatal("msgbuf_write");
+		if (n == 0)	/* Connection closed. */
+			shut = 1;
+	}
+
+	for (;;) {
+		if ((n = imsg_get(ibuf, &imsg)) == -1)
+			fatal("%s: imsg_get error", __func__);
+		if (n == 0)	/* No more messages. */
+			break;
+
+		switch (imsg.hdr.type) {
+		case IMSG_CONTROLFD:
+			if ((fd = imsg.fd) == -1)
+				fatalx("%s: expected to receive imsg "
+				    "control fd but didn't receive any",
+				    __func__);
+			/* Listen on control socket. */
+			control_listen(fd);
+			break;
+		case IMSG_RECONF_CONF:
+		case IMSG_RECONF_PKI:
+		case IMSG_RECONF_PKI_CERT:
+		case IMSG_RECONF_PKI_KEY:
+		case IMSG_RECONF_LISTEN:
+		case IMSG_RECONF_END:
+			listener_receive_config(&imsg, &nconf, &pki);
+			break;
+		case IMSG_AUTH:
+			find.id = imsg.hdr.peerid;
+			client = SPLAY_FIND(clients_tree_id, &clients, &find);
+			if (client == NULL) {
+				if (imsg.fd != -1)
+					close(imsg.fd);
+				break;
+			}
+			if (imsg.fd == -1) {
+				log_info("got fd = -1, auth failed?");
+				close_conn(client);
+				break;
+			}
+			imsg_init(&client->iev.ibuf, imsg.fd);
+			client->iev.events = EV_READ;
+			client->iev.handler = listener_dispatch_client;
+			event_set(&client->iev.ev, client->iev.ibuf.fd,
+			    client->iev.events, client->iev.handler, client);
+			listener_imsg_compose_client(client, IMSG_AUTH,
+			    client->id, imsg.data, IMSG_DATA_SIZE(imsg));
+			break;
+		case IMSG_AUTH_DIR:
+			find.id = imsg.hdr.peerid;
+			client = SPLAY_FIND(clients_tree_id, &clients, &find);
+			if (client == NULL) {
+				log_info("got AUTH_DIR but client gone");
+				break;
+			}
+
+			listener_imsg_compose_client(client, IMSG_AUTH_DIR,
+			    0, imsg.data, IMSG_DATA_SIZE(imsg));
+
+			client->bev = bufferevent_new(client->fd,
+			    client_read, client_write, client_error,
+			    client);
+			if (client->bev == NULL) {
+				log_info("failed to allocate client buffer");
+				close_conn(client);
+				return;
+			}
+
+#if HAVE_EVENT2
+			evbuffer_unfreeze(client->bev->input, 0);
+			evbuffer_unfreeze(client->bev->output, 1);
+#endif
+
+			listen = listen_by_id(client->lid);
+			if (listen->flags & L_TLS) {
+				event_set(&client->bev->ev_read, client->fd,
+				    EV_READ, client_tls_readcb, client->bev);
+				event_set(&client->bev->ev_write, client->fd,
+				    EV_WRITE, client_tls_writecb, client->bev);
+			}
+
+			/*
+			 * Read or write at least a header before
+			 * firing the callbacks.  High watermark of 0
+			 * to never stop reading/writing; probably to
+			 * be revisited.
+			 */
+			/* bufferevent_setwatermark(client->bev, EV_READ|EV_WRITE, */
+			    /* sizeof(struct np_msg_header), 0); */
+			bufferevent_enable(client->bev, EV_READ|EV_WRITE);
+			break;
+
+		default:
+			log_debug("%s: unexpected imsg %d", __func__,
+			    imsg.hdr.type);
+			break;
+		}
+		imsg_free(&imsg);
+	}
+
+	if (!shut)
+		listener_imsg_event_add(iev, d);
+	else {
+		/* This pipe is dead.  Remove its event handler. */
+		event_del(&iev->ev);
+		log_warnx("pipe closed, shutting down...");
+		event_loopexit(NULL);
+	}
+}
+
+int
+listener_imsg_compose_main(int type, uint32_t peerid, const void *data,
+    uint16_t datalen)
+{
+	return imsg_compose_event(iev_main, type, peerid, 0, -1, data,
+	    datalen);
+}
+
+static void
+listener_imsg_event_add(struct imsgev *iev, void *d)
+{
+	iev->events = EV_READ;
+	if (iev->ibuf.w.queued)
+		iev->events |= EV_WRITE;
+
+	event_del(&iev->ev);
+	event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, d);
+	event_add(&iev->ev, NULL);
+}
+
+static void
+listener_dispatch_client(int fd, short event, void *d)
+{
+        struct client	 find, *client = d;
+	struct imsg	 imsg;
+	struct imsgev	*iev;
+	struct imsgbuf	*ibuf;
+	ssize_t		 n;
+	int		 r, shut = 0;
+
+	iev = &client->iev;
+	ibuf = &iev->ibuf;
+
+	if (event & EV_READ) {
+		if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+			fatal("imsg_read error");
+		if (n == 0)	/* Connection closed */
+			shut = 1;
+	}
+
+	if (event & EV_WRITE) {
+		if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+			fatal("msgbuf_write");
+		if (n == 0)	/* Connection closed. */
+			shut = 1;
+	}
+
+	for (;;) {
+		if ((n = imsg_get(ibuf, &imsg)) == -1)
+			fatal("%s: imsg_get error", __func__);
+		if (n == 0)	/* No more messages. */
+			break;
+
+		switch (imsg.hdr.type) {
+		case IMSG_BUF:
+			find.id = imsg.hdr.peerid;
+			client = SPLAY_FIND(clients_tree_id, &clients, &find);
+			if (client == NULL) {
+				log_info("got IMSG_BUF but client (%d) gone",
+				    imsg.hdr.peerid);
+				break;
+			}
+			r = bufferevent_write(client->bev, imsg.data,
+			    IMSG_DATA_SIZE(imsg));
+			if (r == -1) {
+				log_warn("%s: bufferevent_write failed",
+				    __func__);
+				close_conn(client);
+				break;
+			}
+			break;
+
+		case IMSG_MSIZE:
+			if (IMSG_DATA_SIZE(imsg) != sizeof(client->msize))
+				fatal("IMSG_MSIZE size mismatch: "
+				    "got %zu want %zu", IMSG_DATA_SIZE(imsg),
+				    sizeof(client->msize));
+
+			memcpy(&client->msize, imsg.data,
+			    sizeof(client->msize));
+
+			if (client->msize == 0)
+				fatal("IMSG_MSIZE got msize = 0");
+
+			break;
+
+		case IMSG_CLOSE:
+			/*
+			 * Both EVBUFFER_READ or EVBUFFER_WRITE should
+			 * be fine.
+			 */
+			client_error(client->bev, EVBUFFER_READ, client);
+			break;
+
+		default:
+			log_debug("%s: unexpected imsg %d", __func__,
+			    imsg.hdr.type);
+			break;
+		}
+		imsg_free(&imsg);
+	}
+
+	if (!shut)
+		listener_imsg_event_add(iev, d);
+	else {
+		/* This pipe is dead.  Remove its handler */
+		log_debug("client proc vanished");
+                close_conn(client);
+	}
+}
+
+static int
+listener_imsg_compose_client(struct client *client, int type,
+    uint32_t peerid, const void *data, uint16_t len)
+{
+	int ret;
+
+	if ((ret = imsg_compose(&client->iev.ibuf, type, peerid, 0, -1,
+	    data, len)) != -1)
+		listener_imsg_event_add(&client->iev, client);
+
+	return ret;
+}
+
+static inline struct kd_pki_conf *
+pki_by_name(const char *name)
+{
+        struct kd_pki_conf *pki;
+
+	STAILQ_FOREACH(pki, &listener_conf->pki_head, entry) {
+		if (!strcmp(name, pki->name))
+			return pki;
+	}
+
+	return NULL;
+}
+
+static void
+apply_config(struct kd_conf *conf)
+{
+	struct kd_pki_conf *pki;
+	struct kd_listen_conf *listen;
+
+	listener_conf = conf;
+
+	/* prepare the various tls_config */
+	STAILQ_FOREACH(pki, &listener_conf->pki_head, entry) {
+		if ((pki->tlsconf = tls_config_new()) == NULL)
+			fatal("tls_config_new");
+		tls_config_verify_client_optional(pki->tlsconf);
+		tls_config_insecure_noverifycert(pki->tlsconf);
+		if (tls_config_set_keypair_mem(pki->tlsconf,
+		    pki->cert, pki->certlen,
+		    pki->key, pki->keylen) == -1)
+			fatalx("tls_config_set_keypair_mem: %s",
+			    tls_config_error(pki->tlsconf));
+	}
+
+	/* prepare and kickoff the listeners */
+	STAILQ_FOREACH(listen, &listener_conf->listen_head, entry) {
+		if ((listen->ctx = tls_server()) == NULL)
+			fatal("tls_server");
+
+		pki = pki_by_name(listen->pki);
+		if (tls_configure(listen->ctx, pki->tlsconf) == -1)
+			fatalx("tls_configure: %s",
+			    tls_config_error(pki->tlsconf));
+
+		event_set(&listen->ev, listen->fd, EV_READ|EV_PERSIST,
+		    handle_accept, listen);
+		event_add(&listen->ev, NULL);
+	}
+}
+
+static inline void
+yield_r(struct client *c, void (*fn)(int, short, void *))
+{
+	if (event_pending(&c->event, EV_WRITE|EV_READ, NULL))
+		event_del(&c->event);
+	event_set(&c->event, c->fd, EV_READ, fn, c);
+	event_add(&c->event, NULL);
+}
+
+static inline void
+yield_w(struct client *c, void (*fn)(int, short, void *))
+{
+	if (event_pending(&c->event, EV_WRITE|EV_READ, NULL))
+		event_del(&c->event);
+	event_set(&c->event, c->fd, EV_WRITE, fn, c);
+	event_add(&c->event, NULL);
+}
+
+static inline uint32_t
+random_id(void)
+{
+#if HAVE_ARC4RANDOM
+# define RANDID() arc4random()
+#else
+	/* not as pretty as a random id */
+	static uint32_t counter = 0;
+# define RANDID() counter++
+#endif
+
+	struct client find, *res;
+
+	for (;;) {
+		find.id = RANDID();
+		res = SPLAY_FIND(clients_tree_id, &clients, &find);
+		if (res == NULL)
+			return find.id;
+	}
+
+#undef RANDID
+}
+
+static void
+handle_accept(int fd, short ev, void *data)
+{
+	struct kd_listen_conf *listen = data;
+	struct client *c;
+	int s;
+
+	if ((s = accept(fd, NULL, NULL)) == -1) {
+		log_warn("accept");
+		return;
+	}
+
+	c = xcalloc(1, sizeof(*c));
+	c->msize = MSIZE9P;
+	c->lid = listen->id;
+	c->iev.ibuf.fd = -1;
+
+	if (tls_accept_socket(listen->ctx, &c->ctx, s) == -1) {
+		log_warnx("tls_accept_socket: %s",
+		    tls_error(listen->ctx));
+		free(c);
+		close(s);
+		return;
+	}
+
+	c->fd = s;
+	c->id = random_id();
+
+	SPLAY_INSERT(clients_tree_id, &clients, c);
+
+	/* initialize the event */
+	event_set(&c->event, c->fd, EV_READ, NULL, NULL);
+
+	yield_r(c, handle_handshake);
+}
+
+static void
+handle_handshake(int fd, short ev, void *data)
+{
+	struct client *c = data;
+	struct kd_auth_req auth;
+	ssize_t r;
+	const char *hash;
+
+	switch (r = tls_handshake(c->ctx)) {
+	case TLS_WANT_POLLIN:
+		yield_r(c, handle_handshake);
+		return;
+	case TLS_WANT_POLLOUT:
+		yield_w(c, handle_handshake);
+		return;
+	case -1:
+		log_debug("handhsake failed: %s", tls_error(c->ctx));
+		close_conn(c);
+		return;
+	}
+
+	if ((hash = tls_peer_cert_hash(c->ctx)) == NULL) {
+		log_warnx("client didn't provide certificate");
+		close_conn(c);
+		return;
+	}
+
+	memset(&auth, 0, sizeof(auth));
+	auth.listen_id = c->lid;
+	strlcpy(auth.hash, hash, sizeof(auth.hash));
+	log_debug("sending hash %s", auth.hash);
+
+	listener_imsg_compose_main(IMSG_AUTH_TLS, c->id,
+	    &auth, sizeof(auth));
+}
+
+static void
+client_read(struct bufferevent *bev, void *d)
+{
+	struct client	*client = d;
+	struct evbuffer	*src = EVBUFFER_INPUT(bev);
+	uint32_t	 len;
+
+	for (;;) {
+		if (EVBUFFER_LENGTH(src) < 4)
+			return;
+
+		memcpy(&len, EVBUFFER_DATA(src), sizeof(len));
+		len = le32toh(len);
+		log_debug("expecting a message %"PRIu32" bytes long "
+		    "(of wich %zu already read)",
+		    len, EVBUFFER_LENGTH(src));
+
+		if (len < HEADERSIZE) {
+			log_warnx("invalid message size %d (too low)", len);
+			client_error(bev, EVBUFFER_READ, client);
+			return;
+		}
+
+		if (len > client->msize) {
+			log_warnx("incoming message bigger than msize "
+			    "(%"PRIu32" vs %"PRIu32")", len, client->msize);
+			client_error(bev, EVBUFFER_READ, client);
+			return;
+		}
+
+		if (len > EVBUFFER_LENGTH(src))
+			return;
+
+		listener_imsg_compose_client(client, IMSG_BUF, client->id,
+		    EVBUFFER_DATA(src), len);
+		evbuffer_drain(src, len);
+	}
+}
+
+static void
+client_write(struct bufferevent *bev, void *d)
+{
+	/*
+	 * here we can do some fancy logic like deciding when to call
+	 *
+	 *	(*bev->errorcb)(bev, EVBUFFER_WRITE, bev->cbarg)
+	 *
+	 * to signal the end of the transaction.
+	 */
+
+	return;
+}
+
+static void
+client_error(struct bufferevent *bev, short err, void *d)
+{
+	struct client	*client = d;
+	struct evbuffer	*buf;
+
+        if (err & EVBUFFER_ERROR) {
+		if (errno == EFBIG) {
+			bufferevent_enable(bev, EV_READ);
+			return;
+		}
+		log_debug("buffer event error");
+                close_conn(client);
+		return;
+	}
+
+	if (err & EVBUFFER_EOF) {
+                close_conn(client);
+		return;
+	}
+
+	if (err & (EVBUFFER_READ|EVBUFFER_WRITE)) {
+		bufferevent_disable(bev, EV_READ|EV_WRITE);
+		client->done = 1;
+
+		buf = EVBUFFER_OUTPUT(client->bev);
+		if (EVBUFFER_LENGTH(buf) != 0) {
+			/* finish writing all the data first */
+			bufferevent_enable(client->bev, EV_WRITE);
+			return;
+		}
+
+		close_conn(client);
+		return;
+	}
+
+	log_warnx("unknown event error, closing client connection");
+	close_conn(client);
+}
+
+static void
+client_tls_readcb(int fd, short event, void *d)
+{
+	struct bufferevent	*bufev = d;
+	struct client		*client = bufev->cbarg;
+	char			 buf[IBUF_READ_SIZE];
+	int			 what = EVBUFFER_READ;
+	int			 howmuch = IBUF_READ_SIZE;
+	ssize_t			 ret;
+	size_t			 len;
+
+	if (event == EV_TIMEOUT) {
+		what |= EVBUFFER_TIMEOUT;
+		goto err;
+	}
+
+	if (bufev->wm_read.high != 0)
+		howmuch = MIN(sizeof(buf), bufev->wm_read.high);
+
+        switch (ret = tls_read(client->ctx, buf, howmuch)) {
+	case TLS_WANT_POLLIN:
+	case TLS_WANT_POLLOUT:
+		goto retry;
+	case -1:
+		what |= EVBUFFER_ERROR;
+		goto err;
+	}
+	len = ret;
+
+	if (len == 0) {
+		what |= EVBUFFER_EOF;
+		goto err;
+	}
+
+	if (evbuffer_add(bufev->input, buf, len) == -1) {
+		what |= EVBUFFER_ERROR;
+		goto err;
+	}
+
+	event_add(&bufev->ev_read, NULL);
+
+	len = EVBUFFER_LENGTH(bufev->input);
+	if (bufev->wm_read.low != 0 && len < bufev->wm_read.low)
+		return;
+	if (bufev->wm_read.high != 0 && len > bufev->wm_read.high) {
+		/*
+		 * here we could implement some read pressure
+		 * mechanism.
+		 */
+	}
+
+	if (bufev->readcb != NULL)
+		(*bufev->readcb)(bufev, bufev->cbarg);
+
+	return;
+
+retry:
+	event_add(&bufev->ev_read, NULL);
+	return;
+
+err:
+	(*bufev->errorcb)(bufev, what, bufev->cbarg);
+}
+
+static void
+client_tls_writecb(int fd, short event, void *d)
+{
+	struct bufferevent	*bufev = d;
+	struct client		*client = bufev->cbarg;
+	ssize_t			 ret;
+	size_t			 len;
+	short			 what = EVBUFFER_WRITE;
+
+	if (event == EV_TIMEOUT) {
+		what |= EVBUFFER_TIMEOUT;
+		goto err;
+	}
+
+	if (EVBUFFER_LENGTH(bufev->output) != 0) {
+		ret = tls_write(client->ctx,
+		    EVBUFFER_DATA(bufev->output),
+		    EVBUFFER_LENGTH(bufev->output));
+		switch (ret) {
+		case TLS_WANT_POLLIN:
+		case TLS_WANT_POLLOUT:
+			goto retry;
+		case -1:
+			what |= EVBUFFER_ERROR;
+			goto err;
+		}
+		len = ret;
+		evbuffer_drain(bufev->output, len);
+	}
+
+	if (EVBUFFER_LENGTH(bufev->output) != 0)
+		event_add(&bufev->ev_write, NULL);
+
+	if (bufev->writecb != NULL &&
+	    EVBUFFER_LENGTH(bufev->output) <= bufev->wm_write.low)
+		(*bufev->writecb)(bufev, bufev->cbarg);
+	return;
+
+retry:
+	event_add(&bufev->ev_write, NULL);
+	return;
+
+err:
+	(*bufev->errorcb)(bufev, what, bufev->cbarg);
+}
+
+static void
+close_conn(struct client *c)
+{
+	log_debug("closing connection");
+
+	if (c->iev.ibuf.fd != -1) {
+		listener_imsg_compose_client(c, IMSG_CONN_GONE, 0, NULL, 0);
+		imsg_flush(&c->iev.ibuf);
+		msgbuf_clear(&c->iev.ibuf.w);
+		event_del(&c->iev.ev);
+		close(c->iev.ibuf.fd);
+	}
+
+	handle_close(c->fd, 0, c);
+}
+
+static void
+handle_close(int fd, short ev, void *d)
+{
+	struct client *c = d;
+
+	switch (tls_close(c->ctx)) {
+	case TLS_WANT_POLLIN:
+		yield_r(c, handle_close);
+		return;
+	case TLS_WANT_POLLOUT:
+		yield_w(c, handle_close);
+		return;
+	}
+
+	event_del(&c->event);
+	tls_free(c->ctx);
+	close(c->fd);
+	free(c);
+}
blob - /dev/null
blob + 2583bbb2538aa0ddf70b6d0c6ce612bf307d63df (mode 644)
--- /dev/null
+++ kamid/listener.h
@@ -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.
+ */
+
+#ifndef LISTENER_H
+#define LISTENER_H
+
+void		listener(int, int);
+void		listener_dispatch_main(int, short, void *);
+int		listener_imsg_compose_main(int, uint32_t, const void *, uint16_t);
+
+#endif
blob - /dev/null
blob + 342a0800da97c13c5140ccfbd7fe240b178499b9 (mode 644)
--- /dev/null
+++ kamid/parse.y
@@ -0,0 +1,989 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ * Copyright (c) 2018 Florian Obser <florian@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/types.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <imsg.h>
+
+#include "log.h"
+#include "kamid.h"
+#include "table.h"
+#include "utils.h"
+
+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;
+	u_char			*ungetbuf;
+	int			 eof_reached;
+	int			 lineno;
+	int			 errors;
+} *file, *topfile;
+struct file	*pushfile(const char *, int);
+int		 popfile(void);
+int		 check_file_secrecy(int, const char *);
+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);
+
+TAILQ_HEAD(symhead, sym)	 symhead = TAILQ_HEAD_INITIALIZER(symhead);
+struct sym {
+	TAILQ_ENTRY(sym)	 entry;
+	int			 used;
+	int			 persist;
+	char			*nam;
+	char			*val;
+};
+
+int	 symset(const char *, const char *, int);
+char	*symget(const char *);
+
+void	 clear_config(struct kd_conf *xconf);
+
+static void		 add_table(const char *, const char *, const char *);
+static struct table	*findtable(const char *name);
+static void		 add_cert(const char *, const char *);
+static void		 add_key(const char *, const char *);
+static struct kd_listen_conf *listen_new(void);
+
+static uint32_t			 counter;
+static struct table		*table;
+static struct kd_listen_conf	*listener;
+static struct kd_conf		*conf;
+static int			 errors;
+
+typedef struct {
+	union {
+		int64_t		 number;
+		char		*string;
+		struct table	*table;
+	} v;
+	int lineno;
+} YYSTYPE;
+
+%}
+
+%token	AUTH
+%token	CERT
+%token	ERROR
+%token	INCLUDE
+%token	KEY
+%token	LISTEN
+%token	NO
+%token	ON
+%token	PKI PORT
+%token	TABLE TLS
+%token	USERDATA
+%token	VIRTUAL
+%token	YES
+
+%token	<v.string>	STRING
+%token	<v.number>	NUMBER
+%type	<v.number>	yesno
+%type	<v.string>	string
+%type	<v.table>	tableref
+
+%%
+
+grammar		: /* empty */
+		| grammar include '\n'
+		| grammar '\n'
+		| grammar table '\n'
+		| grammar pki '\n'
+		| grammar listen '\n'
+		| grammar varset '\n'
+		| grammar error '\n'		{ 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');
+		}
+		;
+
+string		: string STRING	{
+			if (asprintf(&$$, "%s %s", $1, $2) == -1) {
+				free($1);
+				free($2);
+				yyerror("string: asprintf");
+				YYERROR;
+			}
+			free($1);
+			free($2);
+		}
+		| STRING
+		;
+
+yesno		: YES	{ $$ = 1; }
+		| NO	{ $$ = 0; }
+		;
+
+optnl		: '\n' optnl		/* zero or more newlines */
+		| /*empty*/
+		;
+
+nl		: '\n' optnl		/* one or more newlines */
+		;
+
+arrow		: '=' '>' ;
+
+comma		: ',' optnl
+		;
+
+varset		: STRING '=' string		{
+			char *s = $1;
+			if (verbose)
+				printf("%s = \"%s\"\n", $1, $3);
+			while (*s++) {
+				if (isspace((unsigned char)*s)) {
+					yyerror("macro name cannot contain "
+					    "whitespace");
+					free($1);
+					free($3);
+					YYERROR;
+				}
+			}
+			if (symset($1, $3, 0) == -1)
+				fatal("cannot store variable");
+			free($1);
+			free($3);
+		}
+		;
+
+pki		: PKI STRING CERT STRING { add_cert($2, $4); }
+		| PKI STRING KEY STRING  { add_key($2, $4); }
+		;
+
+table_kp	: string arrow string optnl {
+			if (table_add(table, $1, $3) == -1)
+				yyerror("can't add to table %s",
+				    table->t_name);
+			free($1);
+			free($3);
+		}
+		;
+
+table_kps	: table_kp
+		| table_kp comma table_kps
+		;
+
+stringel	: STRING {
+			if (table_add(table, $1, NULL) == -1)
+				yyerror("can't add to table %s",
+				    table->t_name);
+			free($1);
+		}
+		;
+
+string_list	: stringel
+		| stringel comma string_list
+		;
+
+table_vals	: table_kps
+		| string_list
+		;
+
+table		: TABLE STRING STRING {
+			char *p;
+
+			if ((p = strchr($3, ':')) == NULL) {
+				yyerror("invalid table %s", $2);
+				YYERROR;
+			}
+
+			*p = '\0';
+			add_table($2, $3, p+1);
+			free($2);
+			free($3);
+		}
+		| TABLE STRING {
+			add_table($2, "static", NULL);
+		} '{' optnl table_vals '}' {
+			table = NULL;
+		}
+		;
+
+tableref	: '<' STRING '>' {
+			struct table *t;
+
+			t = findtable($2);
+			free($2);
+			if (t == NULL)
+				YYERROR;
+			$$ = t;
+		}
+		;
+
+listen		: LISTEN { listener = listen_new(); }
+		listen_opts {
+			if (listener->auth_table == NULL)
+				yyerror("missing auth table");
+			if (!(listener->flags & L_TLS))
+				yyerror("can't define a non-tls listener");
+			listener = NULL;
+		}
+		;
+
+listen_opts	: listen_opt
+		| listen_opt listen_opts
+		;
+
+listen_opt	: ON STRING PORT NUMBER	{
+			if (*listener->iface != '\0')
+				yyerror("listen address and port already"
+				    " defined");
+			strlcpy(listener->iface, $2, sizeof(listener->iface));
+			listener->port = $4;
+		}
+		| TLS PKI STRING {
+			if (*listener->pki != '\0')
+				yyerror("listen tls pki already defined");
+			listener->flags |= L_TLS;
+			strlcpy(listener->pki, $3, sizeof(listener->pki));
+		}
+		| AUTH tableref {
+			if (listener->auth_table != NULL)
+				yyerror("listen auth already defined");
+			listener->auth_table = $2;
+		}
+		| USERDATA tableref {
+			if (listener->userdata_table != NULL)
+				yyerror("userdata table already defined");
+			listener->userdata_table = $2;
+		}
+		| VIRTUAL tableref {
+			if (listener->virtual_table != NULL)
+				yyerror("virtual table already defined");
+			listener->virtual_table = $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)
+		fatalx("yyerror vasprintf");
+	va_end(ap);
+	logit(LOG_CRIT, "%s:%d: %s", 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[] = {
+		{"auth",		AUTH},
+		{"cert",		CERT},
+		{"include",		INCLUDE},
+		{"key",			KEY},
+		{"listen",		LISTEN},
+		{"no",			NO},
+		{"on",			ON},
+		{"pki",			PKI},
+		{"port",		PORT},
+		{"table",		TABLE},
+		{"tls",			TLS},
+		{"userdata",		USERDATA},
+		{"virtual",		VIRTUAL},
+		{"yes",			YES},
+	};
+	const struct keywords	*p;
+
+	p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
+	    sizeof(keywords[0]), kw_cmp);
+
+	if (p)
+		return p->k_val;
+	else
+		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, next;
+
+	if (quotec) {
+		if ((c = igetc()) == EOF) {
+			yyerror("reached end of file while parsing "
+			    "quoted string");
+			if (file == topfile || popfile() == EOF)
+				return EOF;
+			return quotec;
+		}
+		return c;
+	}
+
+	while ((c = igetc()) == '\\') {
+		next = igetc();
+		if (next != '\n') {
+			c = next;
+			break;
+		}
+		yylval.lineno = file->lineno;
+		file->lineno++;
+	}
+
+	if (c == EOF) {
+		/*
+		 * Fake EOL when hit EOF for the first time. This gets line
+		 * count right if last line in 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, "lungetc");
+		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;
+}
+
+#if 0
+int my_yylex(void);
+
+int
+yylex(void)
+{
+	int x;
+
+	switch (x = my_yylex()) {
+	case AUTH:
+		puts("auth");
+		break;
+	case CERT:
+		puts("cert");
+		break;
+	case ERROR:
+		puts("error");
+		break;
+	case INCLUDE:
+		puts("include");
+		break;
+	case KEY:
+		puts("key");
+		break;
+	case LISTEN:
+		puts("listen");
+		break;
+	case NO:
+		puts("no");
+		break;
+	case ON:
+		puts("on");
+		break;
+	case PKI:
+		puts("pki");
+		break;
+	case PORT:
+		puts("port");
+		break;
+	case TABLE:
+		puts("table");
+		break;
+	case TLS:
+		puts("tls");
+		break;
+	case YES:
+		puts("yes");
+		break;
+	case STRING:
+		printf("string \"%s\"\n", yylval.v.string);
+		break;
+	case NUMBER:
+		printf("number %"PRIi64"\n", yylval.v.number);
+	default:
+		printf("character ");
+		if (x == '\n')
+			printf("\\n");
+		else
+			printf("%c", x);
+		printf(" [0x%x]", x);
+		printf("\n");
+		break;
+	}
+
+	return x;
+}
+
+int
+my_yylex(void)
+#else
+int
+yylex(void)
+#endif
+{
+	char	 buf[8096];
+	char	*p, *val;
+	int	 quotec, next, c;
+	int	 token;
+
+top:
+	p = buf;
+	while ((c = lgetc(0)) == ' ' || c == '\t')
+		; /* nothing */
+
+	yylval.lineno = file->lineno;
+	if (c == '#')
+		while ((c = lgetc(0)) != '\n' && c != EOF)
+			; /* nothing */
+	if (c == '$' && !expanding) {
+		while (1) {
+			if ((c = lgetc(0)) == EOF)
+				return 0;
+
+			if (p + 1 >= buf + sizeof(buf) - 1) {
+				yyerror("string too long");
+				return findeol();
+			}
+			if (isalnum(c) || c == '_') {
+				*p++ = c;
+				continue;
+			}
+			*p = '\0';
+			lungetc(c);
+			break;
+		}
+		val = symget(buf);
+		if (val == NULL) {
+			yyerror("macro '%s' not defined", buf);
+			return findeol();
+		}
+		p = val + strlen(val) - 1;
+		lungetc(DONE_EXPAND);
+		while (p >= val) {
+			lungetc((unsigned char)*p);
+			p--;
+		}
+		lungetc(START_EXPAND);
+		goto top;
+	}
+
+	switch (c) {
+	case '\'':
+	case '"':
+		quotec = c;
+		while (1) {
+			if ((c = lgetc(quotec)) == EOF)
+				return 0;
+			if (c == '\n') {
+				file->lineno++;
+				continue;
+			} else if (c == '\\') {
+				if ((next = lgetc(quotec)) == EOF)
+					return (0);
+				if (next == quotec || next == ' ' ||
+				    next == '\t')
+					c = next;
+				else if (next == '\n') {
+					file->lineno++;
+					continue;
+				} else
+					lungetc(next);
+			} else if (c == quotec) {
+				*p = '\0';
+				break;
+			} else if (c == '\0') {
+				yyerror("syntax error");
+				return findeol();
+			}
+			if (p + 1 >= buf + sizeof(buf) - 1) {
+				yyerror("string too long");
+				return findeol();
+			}
+			*p++ = c;
+		}
+		yylval.v.string = strdup(buf);
+		if (yylval.v.string == NULL)
+			err(1, "yylex: strdup");
+		return STRING;
+	}
+
+#define allowed_to_end_number(x) \
+	(isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
+
+	if (c == '-' || isdigit(c)) {
+		do {
+			*p++ = c;
+			if ((size_t)(p-buf) >= sizeof(buf)) {
+				yyerror("string too long");
+				return findeol();
+			}
+		} while ((c = lgetc(0)) != EOF && isdigit(c));
+		lungetc(c);
+		if (p == buf + 1 && buf[0] == '-')
+			goto nodigits;
+		if (c == EOF || allowed_to_end_number(c)) {
+			const char *errstr = NULL;
+
+			*p = '\0';
+			yylval.v.number = strtonum(buf, LLONG_MIN,
+			    LLONG_MAX, &errstr);
+			if (errstr) {
+				yyerror("\"%s\" invalid number: %s",
+				    buf, errstr);
+				return findeol();
+			}
+			return NUMBER;
+		} else {
+nodigits:
+			while (p > buf + 1)
+				lungetc((unsigned char)*--p);
+			c = (unsigned char)*--p;
+			if (c == '-')
+				return c;
+		}
+	}
+
+#define allowed_in_string(x) \
+	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
+	x != '{' && x != '}' && \
+	x != '!' && x != '=' && x != '#' && \
+	x != ',' && x != '>'))
+
+	if (isalnum(c) || c == ':' || c == '_') {
+		do {
+			*p++ = c;
+			if ((size_t)(p-buf) >= sizeof(buf)) {
+				yyerror("string too long");
+				return findeol();
+			}
+		} while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
+		lungetc(c);
+		*p = '\0';
+		if ((token = lookup(buf)) == STRING)
+			if ((yylval.v.string = strdup(buf)) == NULL)
+				err(1, "yylex: strdup");
+		return token;
+	}
+	if (c == '\n') {
+		yylval.lineno = file->lineno;
+		file->lineno++;
+	}
+	if (c == EOF)
+		return 0;
+	return c;
+}
+
+int
+check_file_secrecy(int fd, const char *fname)
+{
+	struct stat	st;
+
+	if (fstat(fd, &st)) {
+		log_warn("cannot stat %s", fname);
+		return -1;
+	}
+	if (st.st_uid != 0 && st.st_uid != getuid()) {
+		log_warnx("%s: owner not root or current user", fname);
+		return -1;
+	}
+	if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
+		log_warnx("%s: group writable or world read/writable", fname);
+		return -1;
+	}
+	return 0;
+}
+
+struct file *
+pushfile(const char *name, int secret)
+{
+	struct file	*nfile;
+
+	if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
+		log_warn("calloc");
+		return NULL;
+	}
+	if ((nfile->name = strdup(name)) == NULL) {
+		log_warn("strdup");
+		free(nfile);
+		return NULL;
+	}
+	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
+		log_warn("%s", nfile->name);
+		free(nfile->name);
+		free(nfile);
+		return NULL;
+	} else if (secret &&
+	    check_file_secrecy(fileno(nfile->stream), nfile->name)) {
+		fclose(nfile->stream);
+		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) {
+		log_warn("malloc");
+		fclose(nfile->stream);
+		free(nfile->name);
+		free(nfile);
+		return NULL;
+	}
+	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;
+}
+
+struct kd_conf *
+parse_config(const char *filename)
+{
+	struct sym		*sym, *next;
+
+	counter = 0;
+	conf = config_new_empty();
+
+	file = pushfile(filename, 0);
+	if (file == NULL) {
+		free(conf);
+		return NULL;
+	}
+	topfile = file;
+
+	yyparse();
+	errors = file->errors;
+	popfile();
+
+	/* Free macros and check which have not been used. */
+	TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) {
+		if (verbose && !sym->used)
+			fprintf(stderr, "warning: macro '%s' not used\n",
+			    sym->nam);
+		if (!sym->persist) {
+			free(sym->nam);
+			free(sym->val);
+			TAILQ_REMOVE(&symhead, sym, entry);
+			free(sym);
+		}
+	}
+
+	if (errors) {
+		clear_config(conf);
+		return NULL;
+	}
+
+	return conf;
+}
+
+int
+symset(const char *nam, const char *val, int persist)
+{
+	struct sym	*sym;
+
+	TAILQ_FOREACH(sym, &symhead, entry) {
+		if (strcmp(nam, sym->nam) == 0)
+			break;
+	}
+
+	if (sym != NULL) {
+		if (sym->persist == 1)
+			return 0;
+		else {
+			free(sym->nam);
+			free(sym->val);
+			TAILQ_REMOVE(&symhead, sym, entry);
+			free(sym);
+		}
+	}
+	if ((sym = calloc(1, sizeof(*sym))) == NULL)
+		return -1;
+
+	sym->nam = strdup(nam);
+	if (sym->nam == NULL) {
+		free(sym);
+		return -1;
+	}
+	sym->val = strdup(val);
+	if (sym->val == NULL) {
+		free(sym->nam);
+		free(sym);
+		return -1;
+	}
+	sym->used = 0;
+	sym->persist = persist;
+	TAILQ_INSERT_TAIL(&symhead, sym, entry);
+	return 0;
+}
+
+int
+cmdline_symset(char *s)
+{
+	char	*sym, *val;
+	int	ret;
+
+	if ((val = strrchr(s, '=')) == NULL)
+		return -1;
+	sym = strndup(s, val - s);
+	if (sym == NULL)
+		errx(1, "%s: strndup", __func__);
+	ret = symset(sym, val + 1, 1);
+	free(sym);
+
+	return ret;
+}
+
+char *
+symget(const char *nam)
+{
+	struct sym	*sym;
+
+	TAILQ_FOREACH(sym, &symhead, entry) {
+		if (strcmp(nam, sym->nam) == 0) {
+			sym->used = 1;
+			return sym->val;
+		}
+	}
+	return NULL;
+}
+
+void
+clear_config(struct kd_conf *xconf)
+{
+	/* free stuff? */
+
+	free(xconf);
+}
+
+static void
+add_table(const char *name, const char *type, const char *path)
+{
+	if (table_open(conf, name, type, path) == -1)
+		yyerror("can't initialize table %s", name);
+	table = STAILQ_FIRST(&conf->table_head)->table;
+}
+
+static struct table *
+findtable(const char *name)
+{
+	struct kd_tables_conf *i;
+
+	STAILQ_FOREACH(i, &conf->table_head, entry) {
+		if (!strcmp(i->table->t_name, name))
+			return i->table;
+	}
+
+	yyerror("unknown table %s", name);
+	return NULL;
+}
+
+static void
+add_cert(const char *name, const char *path)
+{
+	struct kd_pki_conf *pki;
+
+	STAILQ_FOREACH(pki, &conf->pki_head, entry) {
+		if (strcmp(name, pki->name) != 0)
+			continue;
+
+		if (pki->cert != NULL) {
+			yyerror("duplicate `pki %s cert'", name);
+			return;
+		}
+
+		goto set;
+	}
+
+	pki = xcalloc(1, sizeof(*pki));
+	strlcpy(pki->name, name, sizeof(pki->name));
+	STAILQ_INSERT_HEAD(&conf->pki_head, pki, entry);
+
+set:
+	if ((pki->cert = tls_load_file(path, &pki->certlen, NULL)) == NULL)
+		fatal(NULL);
+}
+
+static void
+add_key(const char *name, const char *path)
+{
+	struct kd_pki_conf *pki;
+
+	STAILQ_FOREACH(pki, &conf->pki_head, entry) {
+		if (strcmp(name, pki->name) != 0)
+			continue;
+
+		if (pki->key != NULL) {
+			yyerror("duplicate `pki %s key'", name);
+			return;
+		}
+
+		goto set;
+	}
+
+	pki = xcalloc(1, sizeof(*pki));
+	strlcpy(pki->name, name, sizeof(pki->name));
+	STAILQ_INSERT_HEAD(&conf->pki_head, pki, entry);
+
+set:
+	if ((pki->key = tls_load_file(path, &pki->keylen, NULL)) == NULL)
+		fatal(NULL);
+}
+
+static struct kd_listen_conf *
+listen_new(void)
+{
+	struct kd_listen_conf *l;
+
+	l = xcalloc(1, sizeof(*l));
+	l->id = counter++;
+	l->fd = -1;
+
+	STAILQ_INSERT_HEAD(&conf->listen_head, l, entry);
+	return l;
+}
blob - /dev/null
blob + e7c8a5965571c231fcca030c4048a11e0f7bfd28 (mode 644)
--- /dev/null
+++ kamid/table.c
@@ -0,0 +1,105 @@
+/*
+ * 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/queue.h>
+#include <sys/uio.h>
+
+#include <event.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <imsg.h>
+
+#include "log.h"
+#include "utils.h"
+#include "kamid.h"
+
+#include "table.h"
+
+int
+table_open(struct kd_conf *conf, const char *name, const char *type,
+    const char *path)
+{
+	struct table *t;
+	struct kd_tables_conf *entry;
+	struct table_backend *backends[] = {
+		&table_static,
+		NULL,
+	}, *b;
+	size_t i;
+
+	for (i = 0; backends[i] != NULL; ++i) {
+		b = backends[i];
+		if (!strcmp(type, b->name))
+			goto found;
+	}
+	log_warn("unknown table type %s", type);
+	return -1;
+
+found:
+	if (b->open == NULL) {
+		log_warn("can't open table %s (type %s)",
+		    name, b->name);
+		return -1;
+	}
+
+	t = xcalloc(1, sizeof(*t));
+	strlcpy(t->t_name, name, sizeof(t->t_name));
+	if (path != NULL)
+		strlcpy(t->t_path, path, sizeof(t->t_path));
+	t->t_backend = b;
+
+	if (t->t_backend->open(t) == -1)
+		fatal("can't open table %s (type %s)",
+		    name, path);
+
+	entry = xcalloc(1, sizeof(*entry));
+	entry->table = t;
+	STAILQ_INSERT_HEAD(&conf->table_head, entry, entry);
+	return 0;
+}
+
+int
+table_add(struct table *t, const char *key, const char *val)
+{
+	if (t->t_backend->add == NULL) {
+		log_warn("can't add to table %s (type %s)",
+		    t->t_name, t->t_backend->name);
+		return -1;
+	}
+
+	return t->t_backend->add(t, key, val);
+}
+
+int
+table_lookup(struct table *t, const char *key, char **ret_val)
+{
+	if (t->t_backend->lookup == NULL) {
+		log_warn("can't lookup table %s (type %s)",
+		    t->t_name, t->t_backend->name);
+		return -1;
+	}
+
+	return t->t_backend->lookup(t, key, ret_val);
+}
+
+void
+table_close(struct table *t)
+{
+	if (t->t_backend->close != NULL)
+		t->t_backend->close(t);
+}
blob - /dev/null
blob + 441a98c2d2203d5c5de63b34018689b42d132d86 (mode 644)
--- /dev/null
+++ kamid/table.h
@@ -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.
+ */
+
+#ifndef TABLE_H
+#define TABLE_H
+
+int	 table_open(struct kd_conf *, const char *, const char *, const char *);
+int	 table_add(struct table *, const char *, const char *);
+int	 table_lookup(struct table *, const char *, char **);
+void	 table_close(struct table *);
+
+#endif
blob - /dev/null
blob + e83954e7280640d8f36108cc971e7028a7600c9c (mode 644)
--- /dev/null
+++ kamid/table_static.c
@@ -0,0 +1,135 @@
+/*
+ * 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 <event.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ohash.h>
+#include <imsg.h>
+
+#include "utils.h"
+#include "kamid.h"
+
+static void	*hash_alloc(size_t, void *);
+static void	*hash_calloc(size_t, size_t, void *);
+static void	 hash_free(void *, void *);
+
+static int	 table_static_open(struct table *);
+static int	 table_static_add(struct table *, const char *, const char *);
+static int	 table_static_lookup(struct table *, const char *, char **);
+static void	 table_static_close(struct table *);
+
+struct table_backend table_static = {
+	"static",
+	table_static_open,
+	table_static_add,
+	table_static_lookup,
+	table_static_close,
+};
+
+struct kp {
+	char	*val;
+	char	 key[];
+};
+
+static void *
+hash_alloc(size_t len, void *d)
+{
+	return xmalloc(len);
+}
+
+static void *
+hash_calloc(size_t nmemb, size_t size, void *d)
+{
+	return xcalloc(nmemb, size);
+}
+
+static void
+hash_free(void *ptr, void *d)
+{
+	free(ptr);
+}
+
+static int
+table_static_open(struct table *t)
+{
+	struct ohash_info info = {
+		.key_offset = offsetof(struct kp, key),
+		.calloc = hash_calloc,
+		.free = hash_free,
+		.alloc = hash_alloc,
+	};
+
+	t->t_handle = xmalloc(sizeof(struct ohash));
+	ohash_init(t->t_handle, 5, &info);
+	return 0;
+}
+
+int
+table_static_add(struct table *t, const char *key, const char *val)
+{
+	struct kp	*kp;
+	unsigned int	 slot;
+
+	if (key == NULL)
+		return -1;
+
+	kp = xcalloc(1, sizeof(*kp) + strlen(key) + 1);
+	strcpy(kp->key, key);
+	if (val != NULL)
+		kp->val = xstrdup(val);
+
+	slot = ohash_qlookup(t->t_handle, kp->key);
+	ohash_insert(t->t_handle, slot, kp);
+
+	return 0;
+}
+
+int
+table_static_lookup(struct table *t, const char *key, char **ret_val)
+{
+	struct kp	*kp;
+	unsigned int	 slot;
+
+	slot = ohash_qlookup(t->t_handle, key);
+	if ((kp = ohash_find(t->t_handle, slot)) == NULL)
+		return -1;
+
+	*ret_val = xstrdup(kp->val);
+	return 0;
+}
+
+static void
+table_static_close(struct table *t)
+{
+	struct kp	*kp;
+	unsigned int	 i;
+
+	for (kp = ohash_first(t->t_handle, &i);
+	     kp != NULL;
+	     kp = ohash_next(t->t_handle, &i)) {
+		ohash_remove(t->t_handle, i);
+		free(kp->key);
+		free(kp->val);
+		free(kp);
+	}
+
+	free(t->t_handle);
+}
blob - fe44bea30966734e0e1715e60e70f6496cb0d631 (mode 644)
blob + /dev/null
--- client.c
+++ /dev/null
@@ -1,1622 +0,0 @@
-/*
- * 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 "compat.h"
-
-#include <sys/stat.h>
-
-#include <dirent.h>
-#include <endian.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <pwd.h>
-#include <signal.h>
-#include <stdlib.h>
-#include <string.h>
-#include <syslog.h>
-#include <unistd.h>
-
-#include "client.h"
-#include "kamid.h"
-#include "log.h"
-#include "sandbox.h"
-#include "utils.h"
-
-/*
- * XXX: atm is difficult to accept messages bigger than MAX_IMSGSIZE
- * minus IMSG_HEADER_SIZE, we need something to split messages into
- * chunks and receive them one by the other.
- *
- * CLIENT_MSIZE is thus the maximum message size we can handle now.
- */
-#define CLIENT_MSIZE (MAX_IMSGSIZE - IMSG_HEADER_SIZE)
-
-#define DEBUG_PACKETS 0
-
-/* straight outta /src/usr.bin/ssh/scp.c */
-#define TYPE_OVERFLOW(type, val) \
-	((sizeof(type) == 4 && (val) > INT32_MAX) || \
-	 (sizeof(type) == 8 && (val) > INT64_MAX) || \
-	 (sizeof(type) != 4 && sizeof(type) != 8))
-
-STAILQ_HEAD(dirhead, dir) dirs;
-struct dir {
-	int			 refcount;
-	int			 fd;
-	STAILQ_ENTRY(dir)	 entries;
-};
-
-STAILQ_HEAD(fidhead, fid) fids;
-struct fid {
-	uint32_t		 fid;
-
-	char			 fpath[PATH_MAX];
-
-	/*
-	 * the flags passed to open(2).  O_CLOEXEC means ORCLOSE, that
-	 * is to unlink the file upon Tclunk.
-	 */
-	int			 iomode;
-
-	/*
-	 * if fd is not -1 this fid was opened, fd represents its
-	 * file descriptor and iomode the flags passed to open(2).
-	 */
-	int			 fd;
-	DIR			*d;
-	struct evbuffer		*evb;
-
-	/*
-	 * expected offset for Tread against a directory.
-	 */
-	uint64_t		 offset;
-
-	struct qid		 qid;
-	struct dir		*dir;
-	STAILQ_ENTRY(fid)	 entries;
-};
-
-static struct imsgev	*iev_listener;
-static struct evbuffer	*evb;
-static uint32_t		 peerid;
-
-static int		 handshaked;
-uint32_t		 msize;
-
-static __dead void	client_shutdown(void);
-static void		client_sig_handler(int, short, void *);
-static void		client_dispatch_listener(int, short, void *);
-static void		client_privdrop(const char *, const char *);
-
-static int		client_send_listener(int, const void *, uint16_t);
-
-static void		 qid_update_from_sb(struct qid *, struct stat *);
-
-static struct dir	*new_dir(int);
-static struct dir	*dir_incref(struct dir *);
-static void		 dir_decref(struct dir *);
-
-static struct fid	*new_fid(struct dir *, uint32_t, const char *, struct qid *);
-static struct fid	*fid_by_id(uint32_t);
-static void		 free_fid(struct fid *);
-
-static void		parse_message(const uint8_t *, size_t,
-			    struct np_msg_header *, uint8_t **);
-
-static void		np_write16(struct evbuffer *, uint16_t);
-static void		np_write32(struct evbuffer *, uint32_t);
-static void		np_write64(struct evbuffer *, uint64_t);
-static void		np_header(uint32_t, uint8_t, uint16_t);
-static void		np_string(struct evbuffer *, uint16_t, const char *);
-static void		np_qid(struct evbuffer *, struct qid *);
-static void		do_send(void);
-
-static void		np_version(uint16_t, uint32_t, const char *);
-static void		np_attach(uint16_t, struct qid *);
-static void		np_clunk(uint16_t);
-static void		np_flush(uint16_t);
-static void		np_walk(uint16_t, int, struct qid *);
-static void		np_open(uint16_t, struct qid *, uint32_t);
-static void		np_create(uint16_t, struct qid *, uint32_t);
-static void		np_read(uint16_t, uint32_t, void *);
-static void		np_write(uint16_t, uint32_t);
-static void		np_stat(uint16_t, uint32_t, void *);
-static void		np_remove(uint16_t);
-static void		np_error(uint16_t, const char *);
-static void		np_errno(uint16_t);
-
-static int	np_read8(const char *, const char *, uint8_t *,
-		    const uint8_t **, size_t *);
-static int	np_read16(const char *, const char *, uint16_t *,
-		    const uint8_t **, size_t *);
-static int	np_read32(const char *, const char *, uint32_t *,
-		    const uint8_t **, size_t *);
-static int	np_read64(const char *, const char *, uint64_t *,
-		    const uint8_t **, size_t *);
-
-#define READSTRERR	-1
-#define READSTRTRUNC	-2
-static int	np_readstr(const char *, const char *, char *, size_t,
-		    const uint8_t **, size_t *);
-
-#define NPREAD8(f, dst, src, len)  np_read8(__func__, f, dst, src, len)
-#define NPREAD16(f, dst, src, len) np_read16(__func__, f, dst, src, len)
-#define NPREAD32(f, dst, src, len) np_read32(__func__, f, dst, src, len)
-#define NPREAD64(f, dst, src, len) np_read64(__func__, f, dst, src, len)
-
-#define NPREADSTR(f, b, bl, src, len) np_readstr(__func__, f, b, bl, src, len)
-
-static void	tversion(struct np_msg_header *, const uint8_t *, size_t);
-static void	tattach(struct np_msg_header *, const uint8_t *, size_t);
-static void	tclunk(struct np_msg_header *, const uint8_t *, size_t);
-static void	tflush(struct np_msg_header *, const uint8_t *, size_t);
-static void	twalk(struct np_msg_header *, const uint8_t *, size_t);
-static void	topen(struct np_msg_header *, const uint8_t *, size_t);
-static void	tcreate(struct np_msg_header *, const uint8_t *, size_t);
-static void	tread(struct np_msg_header *, const uint8_t *, size_t);
-static void	twrite(struct np_msg_header *, const uint8_t *, size_t);
-static void	tstat(struct np_msg_header *, const uint8_t *, size_t);
-static void	tremove(struct np_msg_header *, const uint8_t *, size_t);
-static void	handle_message(struct imsg *, size_t);
-
-__dead void
-client(int debug, int verbose)
-{
-	struct event	ev_sigint, ev_sigterm;
-
-	log_init(debug, LOG_DAEMON);
-	log_setverbose(verbose);
-
-	setproctitle("client");
-	log_procinit("client");
-
-	log_debug("warming up");
-
-	event_init();
-
-	/* Setup signal handlers */
-	signal_set(&ev_sigint, SIGINT, client_sig_handler, NULL);
-	signal_set(&ev_sigterm, SIGTERM, client_sig_handler, NULL);
-
-	signal_add(&ev_sigint, NULL);
-	signal_add(&ev_sigterm, NULL);
-
-	signal(SIGPIPE, SIG_IGN);
-	signal(SIGHUP, SIG_IGN);
-
-	/* Setup pipe and event handler to the listener process */
-	if ((iev_listener = malloc(sizeof(*iev_listener))) == NULL)
-		fatal(NULL);
-
-	imsg_init(&iev_listener->ibuf, 3);
-	iev_listener->handler = client_dispatch_listener;
-
-	/* Setup event handlers. */
-	iev_listener->events = EV_READ;
-	event_set(&iev_listener->ev, iev_listener->ibuf.fd,
-	    iev_listener->events, iev_listener->handler, iev_listener);
-	event_add(&iev_listener->ev, NULL);
-
-	event_dispatch();
-	client_shutdown();
-}
-
-static __dead void
-client_shutdown(void)
-{
-	if (evb != NULL)
-		evbuffer_free(evb);
-
-	msgbuf_clear(&iev_listener->ibuf.w);
-	close(iev_listener->ibuf.fd);
-
-        free(iev_listener);
-
-	log_debug("client exiting");
-	exit(0);
-}
-
-static void
-client_sig_handler(int sig, short event, void *d)
-{
-	/*
-	 * Normal signal handler rules don't apply because libevent
-	 * decouples for us.
-	 */
-
-	switch (sig) {
-	case SIGINT:
-	case SIGTERM:
-		client_shutdown();
-	default:
-		fatalx("unexpected signal %d", sig);
-	}
-}
-
-#define AUTH_NONE 0
-#define AUTH_USER 1
-#define AUTH_DONE 2
-
-static void
-client_dispatch_listener(int fd, short event, void *d)
-{
-	static int		 auth = AUTH_NONE;
-	static char		 username[64] = {0};
-	static char		 dir[PATH_MAX] = {0};
-	struct imsg		 imsg;
-	struct imsgev		*iev = d;
-	struct imsgbuf		*ibuf;
-	ssize_t			 n;
-	int			 shut = 0;
-
-	ibuf = &iev->ibuf;
-
-	if (event & EV_READ) {
-		if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
-			fatal("imsg_read error");
-		if (n == 0)	/* Connection closed */
-			shut = 1;
-	}
-	if (event & EV_WRITE) {
-		if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
-			fatal("msgbuf_write");
-		if (n == 0)	/* Connection closed */
-			shut = 1;
-	}
-
-	for (;;) {
-		if ((n = imsg_get(ibuf, &imsg)) == -1)
-			fatal("%s: imsg_get error", __func__);
-		if (n == 0)	/* No more messages. */
-			break;
-
-		switch (imsg.hdr.type) {
-		case IMSG_AUTH:
-			peerid = imsg.hdr.peerid;
-			if (auth)
-				fatalx("%s: IMSG_AUTH already done", __func__);
-			auth = AUTH_USER;
-			((char *)imsg.data)[IMSG_DATA_SIZE(imsg)-1] = '\0';
-			strlcpy(username, imsg.data, sizeof(username));
-			break;
-		case IMSG_AUTH_DIR:
-			if (auth != AUTH_USER)
-				fatalx("%s: IMSG_AUTH_DIR not after IMSG_AUTH",
-				    __func__);
-			auth = AUTH_DONE;
-			((char *)imsg.data)[IMSG_DATA_SIZE(imsg)-1] = '\0';
-			strlcpy(dir, imsg.data, sizeof(dir));
-			client_privdrop(username, dir);
-			memset(username, 0, sizeof(username));
-			memset(dir, 0, sizeof(username));
-			break;
-		case IMSG_BUF:
-			/* echo! */
-			if (!auth)
-				fatalx("%s: can't handle messages before"
-				    " doing the auth", __func__);
-			handle_message(&imsg, IMSG_DATA_SIZE(imsg));
-			break;
-		case IMSG_CONN_GONE:
-			log_debug("closing");
-			shut = 1;
-			break;
-		default:
-			log_debug("%s: unexpected imsg %d",
-			    __func__, imsg.hdr.type);
-			break;
-		}
-		imsg_free(&imsg);
-	}
-
-	if (!shut)
-		imsg_event_add(iev);
-	else {
-		/* This pipe is dead.  Remove its event handler. */
-		event_del(&iev->ev);
-		log_debug("pipe closed, shutting down...");
-		event_loopexit(NULL);
-	}
-}
-
-static void
-client_privdrop(const char *username, const char *dir)
-{
-	struct passwd *pw;
-
-	setproctitle("client %s", username);
-
-	if ((pw = getpwnam(username)) == NULL)
-		fatalx("getpwnam(%s) failed", username);
-
-	if (chroot(dir) == -1)
-		fatal("chroot");
-	if (chdir("/") == -1)
-		fatal("chdir(\"/\")");
-
-	if (setgroups(1, &pw->pw_gid) ||
-	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
-	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
-		fatal("can't drop privileges");
-
-	sandbox_client();
-	log_debug("client ready; user=%s dir=%s", username, dir);
-
-	if ((evb = evbuffer_new()) == NULL)
-		fatal("evbuffer_new");
-}
-
-static int
-client_send_listener(int type, const void *data, uint16_t len)
-{
-	int ret;
-
-	if ((ret = imsg_compose(&iev_listener->ibuf, type, peerid, 0, -1,
-	    data, len)) != -1)
-		imsg_event_add(iev_listener);
-
-	return ret;
-}
-
-/* set qid fields from sb */
-static void
-qid_update_from_sb(struct qid *qid, struct stat *sb)
-{
-	qid->path = sb->st_ino;
-
-	/*
-	 * Theoretically (and hopefully!) this should be a 64 bit
-	 * number.  Unfortunately, 9P uses 32 bit timestamps.
-	 */
-	qid->vers = sb->st_mtim.tv_sec;
-
-	if (S_ISREG(sb->st_mode))
-		qid->type = QTFILE;
-	else if (S_ISDIR(sb->st_mode))
-		qid->type = QTDIR;
-	else if (S_ISLNK(sb->st_mode))
-		qid->type = QTSYMLINK;
-}
-
-/* creates a qid given a fd */
-static struct dir *
-new_dir(int fd)
-{
-	struct dir	*dir;
-
-	if ((dir = calloc(1, sizeof(*dir))) == NULL)
-		return NULL;
-
-	dir->fd = fd;
-	STAILQ_INSERT_HEAD(&dirs, dir, entries);
-	return dir;
-}
-
-static struct dir *
-dir_incref(struct dir *dir)
-{
-	dir->refcount++;
-	return dir;
-}
-
-static void
-dir_decref(struct dir *dir)
-{
-	if (--dir->refcount > 0)
-		return;
-
-	STAILQ_REMOVE(&dirs, dir, dir, entries);
-
-	close(dir->fd);
-	free(dir);
-}
-
-static struct fid *
-new_fid(struct dir *dir, uint32_t fid, const char *path, struct qid *qid)
-{
-	struct fid	*f;
-	struct qid	 q;
-	struct stat	sb;
-
-	if (qid == NULL) {
-		if (fstatat(dir->fd, path, &sb, 0)) {
-			log_warn("fstatat(%s)", path);
-			return NULL;
-		}
-		qid_update_from_sb(&q, &sb);
-		qid = &q;
-	}
-
-	if ((f = calloc(1, sizeof(*f))) == NULL)
-		return NULL;
-
-	f->dir = dir_incref(dir);
-	f->fid = fid;
-	f->fd = -1;
-
-	strlcpy(f->fpath, path, sizeof(f->fpath));
-
-	memcpy(&f->qid, qid, sizeof(f->qid));
-
-	STAILQ_INSERT_HEAD(&fids, f, entries);
-
-	return f;
-}
-
-static struct fid *
-fid_by_id(uint32_t fid)
-{
-	struct fid	*f;
-
-	STAILQ_FOREACH(f, &fids, entries) {
-		if (f->fid == fid)
-			return f;
-	}
-
-	return NULL;
-}
-
-static void
-free_fid(struct fid *f)
-{
-	int r;
-
-	if (f->fd != -1) {
-		if (f->d != NULL)
-			r = closedir(f->d);
-		else
-			r = close(f->fd);
-
-		if (r == -1)
-			fatal("can't close fid %d", f->fid);
-
-		if (f->evb != NULL)
-			evbuffer_free(f->evb);
-
-		/* try to honour ORCLOSE if requested */
-		if (f->iomode & O_CLOEXEC)
-			unlinkat(f->dir->fd, f->fpath, 0);
-	}
-
-	dir_decref(f->dir);
-
-	STAILQ_REMOVE(&fids, f, fid, entries);
-	free(f);
-}
-
-static void
-parse_message(const uint8_t *data, size_t len, struct np_msg_header *hdr,
-    uint8_t **cnt)
-{
-	size_t olen = len;
-
-	if (!NPREAD32("len", &hdr->len, &data, &len) ||
-	    !NPREAD8("type", &hdr->type, &data, &len) ||
-	    !NPREAD16("tag", &hdr->tag, &data, &len))
-		goto err;
-
-	if (olen != hdr->len)
-		goto err;
-
-	if (hdr->type < Tversion ||
-	    hdr->type >= Tmax    ||
-	    hdr->type == Terror  ||
-	    (hdr->type & 0x1) != 0) /* cannot recv a R* */
-		goto err;
-
-	hdr->tag = le32toh(hdr->tag);
-
-	*cnt = (uint8_t *)data;
-	return;
-
-err:
-	/* TODO: send a proper message to terminate the connection. */
-	fatalx("got invalid message");
-}
-
-static void
-np_write16(struct evbuffer *e, uint16_t x)
-{
-	x = htole16(x);
-	evbuffer_add(e, &x, sizeof(x));
-}
-
-static void
-np_write32(struct evbuffer *e, uint32_t x)
-{
-	x = htole32(x);
-	evbuffer_add(e, &x, sizeof(x));
-}
-
-static void
-np_write64(struct evbuffer *e, uint64_t x)
-{
-	x = htole64(x);
-	evbuffer_add(e, &x, sizeof(x));
-}
-
-static void
-np_writebuf(struct evbuffer *e, size_t len, void *data)
-{
-	evbuffer_add(e, data, len);
-}
-
-static void
-np_header(uint32_t len, uint8_t type, uint16_t tag)
-{
-	len += HEADERSIZE;
-
-	len = htole32(len);
-	tag = htole16(tag);
-
-	evbuffer_add(evb, &len, sizeof(len));
-	evbuffer_add(evb, &type, sizeof(type));
-	evbuffer_add(evb, &tag, sizeof(tag));
-}
-
-static void
-np_string(struct evbuffer *e, uint16_t len, const char *str)
-{
-	uint16_t l = len;
-
-	len = htole16(len);
-	evbuffer_add(e, &len, sizeof(len));
-	evbuffer_add(e, str, l);
-}
-
-static void
-np_qid(struct evbuffer *e, struct qid *qid)
-{
-	uint64_t	path;
-	uint32_t	vers;
-
-	path = htole64(qid->path);
-	vers = htole32(qid->vers);
-
-	evbuffer_add(e, &qid->type, sizeof(qid->type));
-	evbuffer_add(e, &vers, sizeof(vers));
-	evbuffer_add(e, &path, sizeof(path));
-}
-
-static void
-do_send(void)
-{
-	size_t	 len;
-	void	*data;
-
-	len = EVBUFFER_LENGTH(evb);
-	data = EVBUFFER_DATA(evb);
-
-#if DEBUG_PACKETS
-	hexdump("outgoing packet", data, len);
-#endif
-	client_send_listener(IMSG_BUF, data, len);
-	evbuffer_drain(evb, len);
-}
-
-static void
-np_version(uint16_t tag, uint32_t msize, const char *version)
-{
-	uint16_t l;
-
-	l = strlen(version);
-
-	msize = htole32(msize);
-
-	np_header(sizeof(msize) + sizeof(l) + l, Rversion, tag);
-	evbuffer_add(evb, &msize, sizeof(msize));
-	np_string(evb, l, version);
-	do_send();
-}
-
-static void
-np_attach(uint16_t tag, struct qid *qid)
-{
-	np_header(QIDSIZE, Rattach, tag);
-	np_qid(evb, qid);
-	do_send();
-}
-
-static void
-np_clunk(uint16_t tag)
-{
-	np_header(0, Rclunk, tag);
-	do_send();
-}
-
-static void
-np_flush(uint16_t tag)
-{
-	np_header(0, Rflush, tag);
-	do_send();
-}
-
-static void
-np_walk(uint16_t tag, int nwqid, struct qid *wqid)
-{
-	int i;
-
-	/* two bytes for the counter */
-	np_header(2 + QIDSIZE * nwqid, Rwalk, tag);
-	np_write16(evb, nwqid);
-	for (i = 0; i < nwqid; ++i)
-		np_qid(evb, wqid + i);
-
-	do_send();
-}
-
-static void
-np_open(uint16_t tag, struct qid *qid, uint32_t iounit)
-{
-	np_header(QIDSIZE + sizeof(iounit), Ropen, tag);
-	np_qid(evb, qid);
-	np_write32(evb, iounit);
-	do_send();
-}
-
-static void
-np_create(uint16_t tag, struct qid *qid, uint32_t iounit)
-{
-	np_header(QIDSIZE + sizeof(iounit), Rcreate, tag);
-	np_qid(evb, qid);
-	np_write32(evb, iounit);
-	do_send();
-}
-
-static void
-np_read(uint16_t tag, uint32_t count, void *data)
-{
-	if (sizeof(count) + count + HEADERSIZE >= msize) {
-		np_error(tag, "Rread would overflow");
-		return;
-	}
-
-	np_header(sizeof(count) + count, Rread, tag);
-	np_write32(evb, count);
-	np_writebuf(evb, count, data);
-	do_send();
-}
-
-static void
-np_write(uint16_t tag, uint32_t count)
-{
-	np_header(sizeof(count), Rwrite, tag);
-	np_write32(evb, count);
-	do_send();
-}
-
-static void
-np_stat(uint16_t tag, uint32_t count, void *data)
-{
-	if (sizeof(count) + count + HEADERSIZE >= msize) {
-		np_error(tag, "Rstat would overflow");
-		return;
-	}
-
-	np_header(count, Rstat, tag);
-	np_writebuf(evb, count, data);
-	do_send();
-}
-
-static void
-np_remove(uint16_t tag)
-{
-	np_header(0, Rremove, tag);
-	do_send();
-}
-
-static void
-np_error(uint16_t tag, const char *errstr)
-{
-	uint16_t l;
-
-	l = strlen(errstr);
-
-	np_header(sizeof(l) + l, Rerror, tag);
-	np_string(evb, l, errstr);
-	do_send();
-}
-
-static void
-np_errno(uint16_t tag)
-{
-	int saved_errno;
-	char buf[NL_TEXTMAX] = {0};
-
-	saved_errno = errno;
-
-	strerror_r(errno, buf, sizeof(buf));
-	np_error(tag, buf);
-
-	errno = saved_errno;
-}
-
-static int
-np_read8(const char *t, const char *f, uint8_t *dst, const uint8_t **src,
-    size_t *len)
-{
-	if (*len < sizeof(*dst)) {
-		log_warnx("%s: wanted %zu bytes for the %s field but only "
-		    "%zu are available.", t, sizeof(*dst), f, *len);
-		return -1;
-	}
-
-	memcpy(dst, *src, sizeof(*dst));
-	*src += sizeof(*dst);
-	*len -= sizeof(*dst);
-
-	return 1;
-}
-
-static int
-np_read16(const char *t, const char *f, uint16_t *dst, const uint8_t **src,
-    size_t *len)
-{
-	if (*len < sizeof(*dst)) {
-		log_warnx("%s: wanted %zu bytes for the %s field but only "
-		    "%zu are available.", t, sizeof(*dst), f, *len);
-		return -1;
-	}
-
-	memcpy(dst, *src, sizeof(*dst));
-	*src += sizeof(*dst);
-	*len -= sizeof(*dst);
-	*dst = le16toh(*dst);
-
-	return 1;
-}
-
-static int
-np_read32(const char *t, const char *f, uint32_t *dst, const uint8_t **src,
-    size_t *len)
-{
-	if (*len < sizeof(*dst)) {
-		log_warnx("%s: wanted %zu bytes for the %s field but only "
-		    "%zu are available.", t, sizeof(*dst), f, *len);
-		return -1;
-	}
-
-	memcpy(dst, *src, sizeof(*dst));
-	*src += sizeof(*dst);
-	*len -= sizeof(*dst);
-	*dst = le32toh(*dst);
-
-	return 1;
-}
-
-static int
-np_read64(const char *t, const char *f, uint64_t *dst, const uint8_t **src,
-    size_t *len)
-{
-	if (*len < sizeof(*dst)) {
-		log_warnx("%s: wanted %zu bytes for the %s field but only "
-		    "%zu are available.", t, sizeof(*dst), f, *len);
-		return -1;
-	}
-
-	memcpy(dst, *src, sizeof(*dst));
-	*src += sizeof(*dst);
-	*len -= sizeof(*dst);
-	*dst = le64toh(*dst);
-
-	return 1;
-}
-
-static int
-np_readstr(const char *t, const char *f, char *res, size_t reslen,
-    const uint8_t **src, size_t *len)
-{
-	uint16_t	sl;
-	char		buf[32];
-
-	strlcpy(buf, f, sizeof(buf));
-	strlcat(buf, "-len", sizeof(buf));
-
-	if (!np_read16(t, buf, &sl, src, len))
-		return READSTRERR;
-
-	if (*len < sl) {
-		log_warnx("%s: wanted %d bytes for the %s field but only "
-		    "%zu are available.", t, sl, f, *len);
-		return READSTRERR;
-	}
-
-	if (*len > reslen-1)
-		return READSTRTRUNC;
-
-	memcpy(res, *src, sl);
-	res[sl] = '\0';
-	*src += sl;
-	*len -= sl;
-
-	return 0;
-}
-
-static void
-tversion(struct np_msg_header *hdr, const uint8_t *data, size_t len)
-{
-	char *dot, version[32];
-
-	if (handshaked)
-		goto err;
-
-	/* msize[4] version[s] */
-	if (!NPREAD32("msize", &msize, &data, &len))
-		goto err;
-
-	switch (NPREADSTR("version", version, sizeof(version), &data, &len)) {
-	case READSTRERR:
-		goto err;
-	case READSTRTRUNC:
-		log_warnx("9P version string too long, truncated");
-		goto mismatch;
-	}
-
-	if ((dot = strchr(version, '.')) != NULL)
-		*dot = '\0';
-
-	if (strcmp(version, VERSION9P) != 0 ||
-	    msize == 0)
-		goto mismatch;
-
-	/* version matched */
-	handshaked = 1;
-	msize = MIN(msize, CLIENT_MSIZE);
-	client_send_listener(IMSG_MSIZE, &msize, sizeof(msize));
-	np_version(hdr->tag, msize, VERSION9P);
-	return;
-
-mismatch:
-	log_warnx("unknown 9P version string: \"%s\", want "VERSION9P,
-	    version);
-	np_version(hdr->tag, MSIZE9P, "unknown");
-	return;
-
-err:
-	client_send_listener(IMSG_CLOSE, NULL, 0);
-	client_shutdown();
-}
-
-static void
-tattach(struct np_msg_header *hdr, const uint8_t *data, size_t len)
-{
-	struct dir	*dir;
-	struct fid	*f;
-	uint32_t	 fid, afid;
-	int		 fd;
-	char		 aname[PATH_MAX];
-
-	/* fid[4] afid[4] uname[s] aname[s] */
-
-	if (!NPREAD32("fid", &fid, &data, &len) ||
-	    !NPREAD32("afid", &afid, &data, &len))
-		goto err;
-
-	/* read the uname but don't actually use it */
-	switch (NPREADSTR("uname", aname, sizeof(aname), &data, &len)) {
-	case READSTRERR:
-		goto err;
-	case READSTRTRUNC:
-		np_error(hdr->tag, "name too long");
-		return;
-	}
-
-	switch (NPREADSTR("aname", aname, sizeof(aname), &data, &len)) {
-	case READSTRERR:
-		goto err;
-	case READSTRTRUNC:
-		np_error(hdr->tag, "name too long");
-		return;
-	}
-
-	if (fid_by_id(fid) != NULL || afid != NOFID) {
-		np_error(hdr->tag, "invalid fid or afid");
-		return;
-	}
-
-	if ((fd = open(aname, O_RDONLY|O_DIRECTORY)) == -1)
-		goto fail;
-
-	if ((dir = new_dir(fd)) == NULL)
-		goto fail;
-
-	log_debug("attached %s to %d", aname, fid);
-
-	if ((f = new_fid(dir, fid, aname, NULL)) == NULL) {
-		dir_decref(dir);
-		goto fail;
-	}
-
-	np_attach(hdr->tag, &f->qid);
-	return;
-
-fail:
-	np_errno(hdr->tag);
-	log_warn("failed to attach %s", aname);
-	return;
-
-err:
-	client_send_listener(IMSG_CLOSE, NULL, 0);
-	client_shutdown();
-}
-
-static void
-tclunk(struct np_msg_header *hdr, const uint8_t *data, size_t len)
-{
-	struct fid	*f;
-	uint32_t	 fid;
-
-	/* fid[4] */
-	if (!NPREAD32("fid", &fid, &data, &len)) {
-		client_send_listener(IMSG_CLOSE, NULL, 0);
-		client_shutdown();
-		return;
-	}
-
-	if ((f = fid_by_id(fid)) == NULL) {
-		np_error(hdr->tag, "invalid fid");
-		return;
-	}
-
-	free_fid(f);
-	np_clunk(hdr->tag);
-}
-
-static void
-tflush(struct np_msg_header *hdr, const uint8_t *data, size_t len)
-{
-	uint16_t	oldtag;
-
-	/*
-	 * We're doing only synchronous I/O.  Tflush is implemented
-	 * only because it's illegal to reply with a Rerror.
-	 */
-
-	/* oldtag[2] */
-	if (len != sizeof(oldtag)) {
-		log_warnx("Tflush with the wrong size: got %zu want %zu",
-		    len, sizeof(oldtag));
-		client_send_listener(IMSG_CLOSE, NULL, 0);
-		client_shutdown();
-		return;
-	}
-
-	np_flush(hdr->tag);
-}
-
-static void
-twalk(struct np_msg_header *hdr, const uint8_t *data, size_t len)
-{
-	struct stat	 sb;
-	struct dir	*dir;
-	struct qid	 wqid[MAXWELEM] = {0};
-	struct fid	*f, *nf;
-	uint32_t	 fid, newfid;
-	uint16_t	 nwname;
-	int		 fd, oldfd, no, nwqid = 0;
-	char		 wnam[PATH_MAX];
-
-	if (!NPREAD32("fid", &fid, &data, &len)       ||
-	    !NPREAD32("newfid", &newfid, &data, &len) ||
-	    !NPREAD16("nwname", &nwname, &data, &len))
-		goto err;
-
-	if (nwname > MAXWELEM) {
-		log_warnx("Twalk: more than %d path elements: %d",
-		    MAXWELEM, nwname);
-		goto err;
-	}
-
-	if ((f = fid_by_id(fid)) == NULL) {
-		np_error(hdr->tag, "invalid fid");
-		return;
-	}
-
-	if (f->fd != -1) {
-		np_error(hdr->tag, "fid already opened for I/O");
-		return;
-	}
-
-	if (fid == newfid)
-		nf = f;
-	else if ((nf = fid_by_id(newfid)) != NULL) {
-		np_error(hdr->tag, "newfid already in use");
-		return;
-	} else
-		nf = NULL;
-
-	/* special case: fid duplication */
-	if (nwname == 0) {
-		/*
-		 * TODO: should we forbid fids duplication when fid ==
-		 * newfid?
-		 */
-		if (nf == NULL &&
-		    (nf = new_fid(f->dir, newfid, f->fpath, &f->qid)) == NULL)
-			fatal("new_fid duplication");
-
-		np_walk(hdr->tag, 0, NULL);
-		return;
-	}
-
-	if (!(f->qid.type & QTDIR)) {
-		np_error(hdr->tag, "fid doesn't represent a directory");
-		return;
-	}
-
-	oldfd = f->dir->fd;
-
-	for (nwqid = 0; nwqid < nwname; nwqid++) {
-		switch (NPREADSTR("wname", wnam, sizeof(wnam), &data, &len)) {
-		case READSTRERR:
-			goto err;
-		case READSTRTRUNC:
-			np_error(hdr->tag, "wname too long");
-			return;
-		}
-
-		if (*wnam == '\0' ||
-		    strchr(wnam, '/') != NULL ||
-		    !strcmp(wnam, ".")) {
-			errno = EINVAL;
-			goto cantopen;
-		}
-
-		if ((fd = openat(oldfd, wnam, O_RDONLY|O_DIRECTORY)) == -1 &&
-		    errno != ENOTDIR)
-			goto cantopen;
-
-		if ((fd == -1 && fstatat(oldfd, wnam, &sb, 0) == -1) ||
-		    (fd != -1 && fstat(fd, &sb) == -1))
-			goto cantopen;
-
-		qid_update_from_sb(&wqid[nwqid], &sb);
-
-		/* reached a file but we still have other components */
-		if (fd == -1 && nwqid+1 < nwname)
-			goto cantopen;
-
-		/* reached the end and found a file */
-		if (fd == -1 && nwqid+1 == nwname)
-			continue;
-
-		if (oldfd != f->dir->fd)
-			close(oldfd);
-		oldfd = fd;
-	}
-
-	/*
-	 * If fd is -1 we've reached a file, otherwise we've just
-	 * reached another directory.  We must pay attention to what
-	 * file descriptor we use to create the dir, because if we've
-	 * reached a file and oldfd is f->dir->fd then we *must* share
-	 * the same dir (it was a walk of one path from a directory to a
-	 * file, otherwise fun is bound to happen as soon as the client
-     	 * closes the fid for the directory but keeps the one for the
-	 * file.
-	 */
-	if (fd == -1 && oldfd == f->dir->fd)
-		dir = f->dir;
-	else if (fd == -1)
-		dir = new_dir(oldfd);
-	else
-		dir = new_dir(fd);
-
-	if (dir == NULL)
-		fatal("new_dir");
-
-	if (nf == NULL) {
-		if ((nf = new_fid(dir, newfid, wnam, &wqid[nwqid-1])) == NULL)
-			fatal("new fid");
-	} else {
-		/* update the dir */
-		dir_decref(nf->dir);
-		nf->dir = dir_incref(dir);
-	}
-
-	np_walk(hdr->tag, nwqid, wqid);
-	return;
-
-cantopen:
-	if (oldfd != f->dir->fd)
-		close(oldfd);
-	no = errno;
-	if (nwqid == 0)
-		np_error(hdr->tag, strerror(no));
-	else
-		np_walk(hdr->tag, nwqid, wqid);
-	return;
-
-err:
-	client_send_listener(IMSG_CLOSE, NULL, 0);
-	client_shutdown();
-}
-
-static inline int
-npmode_to_unix(uint8_t mode, int *flags)
-{
-	switch (mode & 0x0F) {
-	case KOREAD:
-		*flags = O_RDONLY;
-		break;
-	case KOWRITE:
-		*flags = O_WRONLY;
-		break;
-	case KORDWR:
-		*flags = O_RDWR;
-		break;
-	case KOEXEC:
-		log_warnx("tried to open something with KOEXEC");
-		/* fallthrough */
-	default:
-		return -1;
-	}
-
-	if (mode & KOTRUNC)
-		*flags |= O_TRUNC;
-	if (mode & KORCLOSE)
-		*flags |= O_CLOEXEC;
-
-	return 0;
-}
-
-static void
-topen(struct np_msg_header *hdr, const uint8_t *data, size_t len)
-{
-	struct stat	 sb;
-	struct qid	 qid;
-	struct fid	*f;
-	uint32_t	 fid;
-	uint8_t		 mode;
-	const char	*path;
-
-	/* fid[4] mode[1] */
-	if (!NPREAD32("fid", &fid, &data, &len) ||
-	    !NPREAD8("mode", &mode, &data, &len)) {
-		client_send_listener(IMSG_CLOSE, NULL, 0);
-		client_shutdown();
-		return;
-	}
-
-	if ((f = fid_by_id(fid)) == NULL || f->fd != -1) {
-		np_error(hdr->tag, "invalid fid");
-		return;
-	}
-
-	if (npmode_to_unix(mode, &f->iomode) == -1) {
-		np_error(hdr->tag, "invalid mode");
-		return;
-	}
-
-	path = f->fpath;
-	if (f->qid.type & QTDIR)
-		path = ".";
-
-	if ((f->fd = openat(f->dir->fd, path, f->iomode)) == -1) {
-		np_error(hdr->tag, strerror(errno));
-		return;
-	}
-
-	if (fstat(f->fd, &sb) == -1)
-		fatal("fstat");
-
-	if (S_ISDIR(sb.st_mode)) {
-		if ((f->d = fdopendir(f->fd)) == NULL) {
-			np_errno(hdr->tag);
-			close(f->fd);
-			f->fd = -1;
-			return;
-		}
-
-		if ((f->evb = evbuffer_new()) == NULL) {
-			np_errno(hdr->tag);
-			closedir(f->d);
-			f->d = NULL;
-			f->fd = -1;
-		}
-	}
-
-	f->offset = 0;
-
-	qid_update_from_sb(&qid, &sb);
-	np_open(hdr->tag, &qid, sb.st_blksize);
-}
-
-static void
-tcreate(struct np_msg_header *hdr, const uint8_t *data, size_t len)
-{
-	struct stat	 sb;
-	struct qid	 qid;
-	struct fid	*f;
-	uint32_t	 fid, perm;
-	uint8_t		 mode;
-	char		 name[PATH_MAX];
-
-	/* fid[4] name[s] perm[4] mode[1] */
-	if (!NPREAD32("fid", &fid, &data, &len))
-		goto err;
-	switch (NPREADSTR("name", name, sizeof(name), &data, &len)) {
-	case READSTRERR:
-		goto err;
-	case READSTRTRUNC:
-		np_error(hdr->tag, "name too long");
-		return;
-	}
-	if (!NPREAD32("perm", &perm, &data, &len) ||
-	    !NPREAD8("mode", &mode, &data, &len))
-		goto err;
-
-	if (!strcmp(name, ".") || !strcmp(name, "..") ||
-	    strchr(name, '/') != NULL) {
-		np_error(hdr->tag, "invalid name");
-		return;
-	}
-
-	if ((f = fid_by_id(fid)) == NULL || f->fd != -1) {
-		np_error(hdr->tag, "invalid fid");
-		return;
-	}
-
-	if (!(f->qid.type & QTDIR)) {
-		np_error(hdr->tag, "fid doesn't identify a directory");
-		return;
-	}
-
-	if (npmode_to_unix(mode, &f->iomode) == -1) {
-		np_error(hdr->tag, "invalid mode");
-		return;
-	}
-
-	if (f->iomode & O_RDONLY) {
-		np_error(hdr->tag, "can't create a read-only file");
-		return;
-	}
-
-	/* TODO: parse the mode */
-
-	if (perm & 0x80000000) {
-		/* create a directory */
-		f->fd = mkdirat(f->dir->fd, name, 0755);
-	} else {
-		/* create a file */
-		f->fd = openat(f->dir->fd, name, f->iomode | O_CREAT | O_TRUNC,
-		    0644);
-	}
-
-	if (f->fd == -1) {
-		np_errno(hdr->tag);
-		return;
-	}
-
-	if (fstat(f->fd, &sb) == -1)
-		fatal("fstat");
-
-	if (S_ISDIR(sb.st_mode)) {
-		if ((f->d = fdopendir(f->fd)) == NULL) {
-			np_errno(hdr->tag);
-			close(f->fd);
-			f->fd = -1;
-			return;
-		}
-
-		if ((f->evb = evbuffer_new()) == NULL) {
-			np_errno(hdr->tag);
-			closedir(f->d);
-			f->d = NULL;
-			f->fd = -1;
-		}
-	}
-
-	f->offset = 0;
-
-	qid_update_from_sb(&qid, &sb);
-	np_create(hdr->tag, &qid, sb.st_blksize);
-
-	return;
-
-err:
-	client_send_listener(IMSG_CLOSE, NULL, 0);
-	client_shutdown();
-}
-
-static inline void
-serialize_stat(const char *fname, struct stat *sb, struct evbuffer *evb)
-{
-	struct qid	 qid;
-	const char	*uid, *gid, *muid;
-	size_t		 tot;
-	uint16_t	 namlen, uidlen, gidlen, ulen;
-
-	qid_update_from_sb(&qid, sb);
-
-	/* TODO: fill these fields */
-	uid = "";
-	gid = "";
-	muid = "";
-
-	namlen = strlen(fname);
-	uidlen = strlen(uid);
-	gidlen = strlen(gid);
-	ulen = strlen(muid);
-
-	tot = NPSTATSIZ(namlen, uidlen, gidlen, ulen);
-	if (tot > UINT32_MAX) {
-		log_warnx("stat info for dir entry %s would overflow",
-		    fname);
-		return;
-	}
-
-	np_write16(evb, tot);			/*	size[2]		*/
-	np_write16(evb, sb->st_rdev);		/*	type[2]		*/
-	np_write32(evb, sb->st_dev);		/*	dev[4]		*/
-	np_qid(evb, &qid);			/*	qid[13]		*/
-
-	/* XXX: translate? */
-	np_write32(evb, sb->st_mode);		/*	mode[4]		*/
-
-	np_write32(evb, sb->st_atim.tv_sec);	/*	atime[4]	*/
-	np_write32(evb, sb->st_mtim.tv_sec);	/*	mtime[4]	*/
-	np_write64(evb, sb->st_size);		/*	length[8]	*/
-	np_string(evb, namlen, fname);		/*	name[s]		*/
-	np_string(evb, uidlen, uid);		/*	uid[s]		*/
-	np_string(evb, gidlen, gid);		/*	gid[s]		*/
-	np_string(evb, ulen, muid);		/*	muid[s]		*/
-}
-
-static void
-tread(struct np_msg_header *hdr, const uint8_t *data, size_t len)
-{
-	struct fid	*f;
-	ssize_t		 r;
-	size_t		 howmuch;
-	uint64_t	 off;
-	uint32_t	 fid, count;
-	char		 buf[2048];
-
-	/* fid[4] offset[8] count[4] */
-	if (!NPREAD32("fid", &fid, &data, &len) ||
-	    !NPREAD64("offset", &off, &data, &len) ||
-	    !NPREAD32("count", &count, &data, &len)) {
-		client_send_listener(IMSG_CLOSE, NULL, 0);
-		client_shutdown();
-		return;
-	}
-
-	if ((f = fid_by_id(fid)) == NULL || f->fd == -1) {
-		np_error(hdr->tag, "invalid fid");
-		return;
-	}
-
-	if (TYPE_OVERFLOW(off_t, off)) {
-		log_warnx("unexpected off_t size");
-		np_error(hdr->tag, "invalid offset");
-		return;
-	}
-
-	if (f->d == NULL) {
-		/* read a file */
-		howmuch = MIN(sizeof(buf), count);
-		r = pread(f->fd, buf, howmuch, (off_t)off);
-		if (r == -1)
-			np_errno(hdr->tag);
-		else
-			np_read(hdr->tag, r, buf);
-	} else {
-		if (off == 0 && f->offset != 0) {
-			rewinddir(f->d);
-			f->offset = 0;
-			evbuffer_drain(f->evb, EVBUFFER_LENGTH(f->evb));
-		}
-
-		if (off != f->offset) {
-			np_error(hdr->tag, "can't seek in directories");
-			return;
-		}
-
-		while (EVBUFFER_LENGTH(f->evb) < count) {
-			struct dirent *d;
-			struct stat sb;
-
-			if ((d = readdir(f->d)) == NULL)
-				break;
-			if (fstatat(f->fd, d->d_name, &sb, 0) == -1) {
-				warn("fstatat");
-				continue;
-			}
-			serialize_stat(d->d_name, &sb, f->evb);
-		}
-
-		count = MIN(count, EVBUFFER_LENGTH(f->evb));
-		np_read(hdr->tag, count, EVBUFFER_DATA(f->evb));
-		evbuffer_drain(f->evb, count);
-
-		f->offset += count;
-	}
-}
-
-static void
-twrite(struct np_msg_header *hdr, const uint8_t *data, size_t len)
-{
-	struct fid	*f;
-	ssize_t		 r;
-	uint64_t	 off;
-	uint32_t	 fid, count;
-
-	/* fid[4] offset[8] count[4] data[count] */
-	if (!NPREAD32("fid", &fid, &data, &len) ||
-	    !NPREAD64("off", &off, &data, &len) ||
-	    !NPREAD32("count", &count, &data, &len) ||
-	    len != count) {
-		client_send_listener(IMSG_CLOSE, NULL, 0);
-		client_shutdown();
-		return;
-	}
-
-	if ((f = fid_by_id(fid)) == NULL || f->fd == -1) {
-		np_error(hdr->tag, "invalid fid");
-		return;
-	}
-
-	if (!(f->iomode & O_WRONLY) &&
-	    !(f->iomode & O_RDWR)) {
-		np_error(hdr->tag, "fid not opened for writing");
-		return;
-	}
-
-	if (TYPE_OVERFLOW(off_t, off)) {
-		log_warnx("unexpected off_t size");
-		np_error(hdr->tag, "invalid offset");
-		return;
-	}
-
-	if ((r = pwrite(f->fd, data, len, off)) == -1)
-		np_errno(hdr->tag);
-	else
-		np_write(hdr->tag, r);
-}
-
-static void
-tstat(struct np_msg_header *hdr, const uint8_t *data, size_t len)
-{
-	struct evbuffer	*evb;
-	struct stat	 sb;
-	struct fid	*f;
-	int		 r;
-	uint32_t	 fid;
-
-	/* fid[4] */
-	if (!NPREAD32("fid", &fid, &data, &len)) {
-		client_send_listener(IMSG_CLOSE, NULL, 0);
-		client_shutdown();
-		return;
-	}
-
-	/*
-	 * plan9' stat(9P) is not clear on whether the stat is allowed
-	 * on opened fids or not.  We're allowing stat regardless of the
-	 * status of the fid.
-	 */
-
-	if ((f = fid_by_id(fid)) == NULL) {
-		np_error(hdr->tag, "invalid fid");
-		return;
-	}
-
-	if ((evb = evbuffer_new()) == NULL)
-		fatal("evbuffer_new");
-
-	if (f->fd != -1)
-		r = fstat(f->fd, &sb);
-	else if (f->qid.type & QTDIR)
-		r = fstat(f->dir->fd, &sb);
-	else
-		r = fstatat(f->dir->fd, f->fpath, &sb, 0);
-
-	if (r == -1) {
-		np_errno(hdr->tag);
-		evbuffer_free(evb);
-		return;
-	}
-
-	serialize_stat(f->fpath, &sb, evb);
-	np_stat(hdr->tag, EVBUFFER_LENGTH(evb), EVBUFFER_DATA(evb));
-	evbuffer_free(evb);
-}
-
-static void
-tremove(struct np_msg_header *hdr, const uint8_t *data, size_t len)
-{
-	struct fid	*f;
-	uint32_t	 fid;
-	int		 r;
-	char		 dirpath[PATH_MAX + 3];
-
-	/* fid[4] */
-	if (!NPREAD32("fid", &fid, &data, &len)) {
-		client_send_listener(IMSG_CLOSE, NULL, 0);
-		client_shutdown();
-		return;
-	}
-
-	if ((f = fid_by_id(fid)) == NULL) {
-		np_error(hdr->tag, "invalid fid");
-		return;
-	}
-
-	if (f->qid.type & QTDIR) { /* directory */
-		strlcpy(dirpath, "../", sizeof(dirpath));
-		strlcat(dirpath, f->fpath, sizeof(dirpath));
-		r = unlinkat(f->dir->fd, dirpath, AT_REMOVEDIR);
-	} else /* file */
-		r = unlinkat(f->dir->fd, f->fpath, 0);
-
-	if (r == -1)
-		np_errno(hdr->tag);
-	else
-		np_remove(hdr->tag);
-
-	free_fid(f);
-}
-
-static void
-handle_message(struct imsg *imsg, size_t len)
-{
-	struct msg {
-		uint8_t	 type;
-		void	(*fn)(struct np_msg_header *, const uint8_t *, size_t);
-	} msgs[] = {
-		{Tversion,	tversion},
-		{Tattach,	tattach},
-		{Tclunk,	tclunk},
-		{Tflush,	tflush},
-		{Twalk,		twalk},
-		{Topen,		topen},
-		{Tcreate,	tcreate},
-		{Tread,		tread},
-		{Twrite,	twrite},
-		{Tstat,		tstat},
-		{Tremove,	tremove},
-	};
-	struct np_msg_header	 hdr;
-	size_t			 i;
-	uint8_t			*data;
-
-#if DEBUG_PACKETS
-	hexdump("incoming packet", imsg->data, len);
-#endif
-
-	parse_message(imsg->data, len, &hdr, &data);
-	len -= HEADERSIZE;
-
-	log_debug("got request: len=%d type=%d[%s] tag=%d",
-	    hdr.len, hdr.type, pp_msg_type(hdr.type), hdr.tag);
-
-	if (!handshaked && hdr.type != Tversion) {
-		client_send_listener(IMSG_CLOSE, NULL, 0);
-		client_shutdown();
-		return;
-	}
-
-	for (i = 0; i < sizeof(msgs)/sizeof(msgs[0]); ++i) {
-		if (msgs[i].type != hdr.type)
-			continue;
-
-		msgs[i].fn(&hdr, data, len);
-		return;
-	}
-
-	np_error(hdr.tag, "Not supported.");
-}
blob - /dev/null
blob + af396af039bb01b69bd7997b71bc09cd8dec950e (mode 644)
--- /dev/null
+++ kamid-version.mk
@@ -0,0 +1,8 @@
+KAMID_RELEASE=No
+KAMID_VERSION_NUMBER=0.1
+
+.if ${KAMID_RELEASE} == Yes
+KAMID_VERSION=${KAMID_VERSION_NUMBER}
+.else
+KAMID_VERSION=${KAMID_VERSION_NUMBER}-current
+.endif
blob - a007ec7f0ca02d97660866c9201bfe28e6af56a4 (mode 644)
blob + /dev/null
--- client.h
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef CLIENT_H
-#define CLIENT_H
-
-#include "compat.h"
-
-__dead void	client(int, int);
-
-#endif
blob - /dev/null
blob + 690778f94a83a991200822a1c38328ca1cbbe480 (mode 644)
--- /dev/null
+++ kamiftp/Makefile
@@ -0,0 +1,18 @@
+.PATH:${.CURDIR}/../lib
+
+.include "../kamid-version.mk"
+
+PROG=		kamiftp
+SRCS=		9pclib.c ftp.c log.c utils.c
+MAN=		kamiftp.1
+
+CPPFLAGS=	-I${.CURDIR}/../lib -I${.CURDIR}
+
+LDADD+= -levent -lutil -ltls
+DPADD+= ${LIBEVENT} ${LIBUTIL} ${LIBTLS}
+
+.if ${KAMID_RELEASE} != Yes
+NOMAN= Yes
+.endif
+
+.include <bsd.prog.mk>
blob - /dev/null
blob + 8dd79f8941e896cd85d3f57cd9d5cb8a9d6af4a3 (mode 644)
--- /dev/null
+++ kamiftp/ftp.c
@@ -0,0 +1,1012 @@
+/*
+ * 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/ioctl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <netdb.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <tls.h>
+#include <unistd.h>
+#include <util.h>
+
+#if HAVE_LIBREADLINE
+#include <readline/readline.h>
+#include <readline/history.h>
+#endif
+
+#ifndef nitems
+#define nitems(_a)	(sizeof((_a)) / sizeof((_a)[0]))
+#endif
+
+#include "9pclib.h"
+#include "kami.h"
+#include "utils.h"
+#include "log.h"
+
+/* flags */
+int		 tls;
+const char	*crtpath;
+const char	*keypath;
+
+/* state */
+struct tls_config	*tlsconf;
+struct tls		*ctx;
+int			 sock;
+struct evbuffer		*buf;
+struct evbuffer		*dirbuf;
+uint32_t		 msize;
+int			 bell;
+
+volatile sig_atomic_t	 resized;
+int			 tty_p;
+int			 tty_width;
+
+struct np_stat {
+	uint16_t	 type;
+	uint32_t	 dev;
+	struct qid	 qid;
+	uint32_t	 mode;
+	uint32_t	 atime;
+	uint32_t	 mtime;
+	uint64_t	 length;
+	char		*name;
+	char		*uid;
+	char		*gid;
+	char		*muid;
+};
+
+struct progress {
+	uint64_t	max;
+	uint64_t	done;
+};
+
+int pwdfid;
+
+#define ASSERT_EMPTYBUF() assert(EVBUFFER_LENGTH(buf) == 0)
+
+#if HAVE_LIBREADLINE
+static char *
+read_line(const char *prompt)
+{
+	char *line;
+
+again:
+	if ((line = readline(prompt)) == NULL)
+		return NULL;
+	/* XXX: trim spaces? */
+	if (*line == '\0') {
+		free(line);
+		goto again;
+	}
+
+	add_history(line);
+	return line;
+}
+#else
+static char *
+read_line(const char *prompt)
+{
+	char *ch, *line = NULL;
+	size_t linesize = 0;
+	ssize_t linelen;
+
+	printf("%s", prompt);
+	fflush(stdout);
+
+	linelen = getline(&line, &linesize, stdin);
+	if (linelen == -1)
+		return NULL;
+
+	if ((ch = strchr(line, '\n')) != NULL)
+		*ch = '\0';
+	return line;
+}
+#endif
+
+static void
+tty_resized(int signo)
+{
+	resized = 1;
+}
+
+static void __dead
+usage(int ret)
+{
+	fprintf(stderr, "usage: %s [-c] host[:port] [path]\n",
+	    getprogname());
+	fprintf(stderr, "kamid suite version " KAMID_VERSION "\n");
+	exit(ret);
+}
+
+static void
+do_send(void)
+{
+	const void	*buf;
+	size_t		 nbytes;
+	ssize_t		 r;
+
+	while (EVBUFFER_LENGTH(evb) != 0) {
+		buf = EVBUFFER_DATA(evb);
+		nbytes = EVBUFFER_LENGTH(evb);
+
+		if (ctx == NULL) {
+			r = write(sock, buf, nbytes);
+			if (r == 0 || r == -1)
+				errx(1, "EOF");
+		} else {
+			r = tls_write(ctx, buf, nbytes);
+			if (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT)
+				continue;
+			if (r == -1)
+				errx(1, "tls: %s", tls_error(ctx));
+		}
+
+		evbuffer_drain(evb, r);
+	}
+}
+
+static void
+mustread(void *d, size_t len)
+{
+	ssize_t r;
+
+	while (len != 0) {
+		if (ctx == NULL) {
+			r = read(sock, d, len);
+			if (r == 0 || r == -1)
+				errx(1, "EOF");
+		} else {
+			r = tls_read(ctx, d, len);
+			if (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT)
+				continue;
+			if (r == -1)
+				errx(1, "tls: %s", tls_error(ctx));
+		}
+
+		d += r;
+		len -= r;
+	}
+}
+
+static void
+recv_msg(void)
+{
+	uint32_t	len, l;
+	char		tmp[BUFSIZ];
+
+	mustread(&len, sizeof(len));
+	len = le32toh(len);
+	if (len < HEADERSIZE)
+		errx(1, "read message of invalid length %d", len);
+
+	len -= 4; /* skip the length just read */
+
+	while (len != 0) {
+		l = MIN(len, sizeof(tmp));
+		mustread(tmp, l);
+		len -= l;
+		evbuffer_add(buf, tmp, l);
+	}
+}
+
+static uint64_t
+np_read64(struct evbuffer *buf)
+{
+	uint64_t n;
+
+	evbuffer_remove(buf, &n, sizeof(n));
+	return le64toh(n);
+}
+
+static uint32_t
+np_read32(struct evbuffer *buf)
+{
+	uint32_t n;
+
+	evbuffer_remove(buf, &n, sizeof(n));
+	return le32toh(n);
+}
+
+static uint16_t
+np_read16(struct evbuffer *buf)
+{
+	uint16_t n;
+
+	evbuffer_remove(buf, &n, sizeof(n));
+	return le16toh(n);
+}
+
+static uint16_t
+np_read8(struct evbuffer *buf)
+{
+	uint8_t n;
+
+	evbuffer_remove(buf, &n, sizeof(n));
+	return n;
+}
+
+static char *
+np_readstr(struct evbuffer *buf)
+{
+	uint16_t	 len;
+	char		*str;
+
+	len = np_read16(buf);
+	assert(EVBUFFER_LENGTH(buf) >= len);
+
+	if ((str = calloc(1, len+1)) == NULL)
+		err(1, "calloc");
+	evbuffer_remove(buf, str, len);
+	return str;
+}
+
+static void
+np_read_qid(struct evbuffer *buf, struct qid *qid)
+{
+	assert(EVBUFFER_LENGTH(buf) >= QIDSIZE);
+
+	qid->type = np_read8(buf);
+	qid->vers = np_read32(buf);
+	qid->path = np_read64(buf);
+}
+
+static int
+np_read_stat(struct evbuffer *buf, struct np_stat *st)
+{
+	uint16_t size;
+
+	memset(st, 0, sizeof(*st));
+
+	size = np_read16(buf);
+	if (size > EVBUFFER_LENGTH(buf))
+		return -1;
+
+	st->type = np_read16(buf);
+	st->dev = np_read32(buf);
+	np_read_qid(buf, &st->qid);
+	st->mode = np_read32(buf);
+	st->atime = np_read32(buf);
+	st->mtime = np_read32(buf);
+	st->length = np_read64(buf);
+	st->name = np_readstr(buf);
+	st->uid = np_readstr(buf);
+	st->gid = np_readstr(buf);
+	st->muid = np_readstr(buf);
+
+	return 0;
+}
+
+static void
+expect(uint8_t type)
+{
+	uint8_t t;
+
+	t = np_read8(buf);
+	if (t == type)
+		return;
+
+	if (t == Rerror) {
+		char *err;
+
+		/* skip tag */
+		np_read16(buf);
+
+		err = np_readstr(buf);
+		errx(1, "expected %s, got error %s",
+		    pp_msg_type(type), err);
+	}
+
+	errx(1, "expected %s, got msg type %s",
+	    pp_msg_type(type), pp_msg_type(t));
+}
+
+static void
+expect2(uint8_t type, uint16_t tag)
+{
+	uint16_t t;
+
+	expect(type);
+
+	t = np_read16(buf);
+	if (t == tag)
+		return;
+
+	errx(1, "expected tag 0x%x, got 0x%x", tag, t);
+}
+
+static void
+do_version(void)
+{
+	char		*version;
+
+	tversion(VERSION9P, MSIZE9P);
+	do_send();
+	recv_msg();
+	expect2(Rversion, NOTAG);
+
+	msize = np_read32(buf);
+	version = np_readstr(buf);
+
+	if (msize > MSIZE9P)
+		errx(1, "got unexpected msize: %d", msize);
+	if (strcmp(version, VERSION9P))
+		errx(1, "unexpected 9p version: %s", version);
+
+	free(version);
+	ASSERT_EMPTYBUF();
+}
+
+static void
+do_attach(const char *path)
+{
+	const char *user;
+	struct qid qid;
+
+	if (path == NULL)
+		path = "/";
+	if ((user = getenv("USER")) == NULL)
+		user = "flan";
+
+	tattach(pwdfid, NOFID, user, path);
+	do_send();
+	recv_msg();
+	expect2(Rattach, iota_tag);
+	np_read_qid(buf, &qid);
+
+	ASSERT_EMPTYBUF();
+}
+
+static uint32_t
+do_open(uint32_t fid, uint8_t mode)
+{
+	struct qid qid;
+	uint32_t iounit;
+
+	topen(fid, mode);
+	do_send();
+	recv_msg();
+	expect2(Ropen, iota_tag);
+
+	np_read_qid(buf, &qid);
+	iounit = np_read32(buf);
+
+	ASSERT_EMPTYBUF();
+
+	return iounit;
+}
+
+static void
+do_clunk(uint32_t fid)
+{
+	tclunk(fid);
+	do_send();
+	recv_msg();
+	expect2(Rclunk, iota_tag);
+
+	ASSERT_EMPTYBUF();
+}
+
+static void
+dup_fid(int fid, int nfid)
+{
+	uint16_t nwqid;
+
+	twalk(fid, nfid, NULL, 0);
+	do_send();
+	recv_msg();
+	expect2(Rwalk, iota_tag);
+
+	nwqid = np_read16(buf);
+	assert(nwqid == 0);
+
+	ASSERT_EMPTYBUF();
+}
+
+static int
+walk_path(int fid, int newfid, const char *path, struct qid *qid)
+{
+	char *wnames[MAXWELEM], *p, *t;
+	size_t nwname, i;
+	uint16_t nwqid;
+
+	if ((p = strdup(path)) == NULL)
+		err(1, "strdup");
+	t = p;
+
+	/* strip initial ./ */
+	if (t[0] == '.' && t[1] == '/')
+		t += 2;
+
+	for (nwname = 0; nwname < nitems(wnames) &&
+	    (wnames[nwname] = strsep(&t, "/")) != NULL;) {
+		if (*wnames[nwname] != '\0')
+			nwname++;
+	}
+
+	twalk(fid, newfid, (const char **)wnames, nwname);
+	do_send();
+	recv_msg();
+	expect2(Rwalk, iota_tag);
+
+	nwqid = np_read16(buf);
+	assert(nwqid <= nwname);
+
+	/* consume all qids */
+	for (i = 0; i < nwname; ++i)
+		np_read_qid(buf, qid);
+
+	free(p);
+
+	return nwqid == nwname;
+}
+
+static void
+do_stat(int fid, struct np_stat *st)
+{
+	tstat(fid);
+	do_send();
+	recv_msg();
+	expect2(Rstat, iota_tag);
+
+	if (np_read_stat(buf, st) == -1)
+		errx(1, "invalid stat struct read");
+
+	ASSERT_EMPTYBUF();
+}
+
+static size_t
+do_read(int fid, uint64_t off, uint32_t count, void *data)
+{
+	uint32_t r;
+
+	tread(fid, off, count);
+	do_send();
+	recv_msg();
+	expect2(Rread, iota_tag);
+
+	r = np_read32(buf);
+	assert(r == EVBUFFER_LENGTH(buf));
+	assert(r <= count);
+	evbuffer_remove(buf, data, r);
+
+	ASSERT_EMPTYBUF();
+
+	return r;
+}
+
+static void
+draw_progress(const char *pre, const struct progress *p)
+{
+	struct winsize ws;
+	int i, l, w;
+	double perc;
+
+	perc = 100.0 * p->done / p->max;
+	if (!tty_p) {
+		fprintf(stderr, "%s: %d%%\n", pre, (int)perc);
+		return;
+	}
+
+	if (resized) {
+		resized = 0;
+
+		if (ioctl(0, TIOCGWINSZ, &ws) == -1)
+			return;
+		tty_width = ws.ws_col;
+	}
+	w = tty_width;
+
+	if (pre == NULL ||
+	    ((l = printf("\r%s ", pre)) == -1 || l >= w))
+		return;
+
+	w -= l + 2 + 5; /* 2 for |, 5 for percentage + \n */
+	if (w < 0) {
+		printf("%4d%%\n", (int)perc);
+		return;
+	}
+
+	printf("|");
+
+	l = w * MIN(100.0, perc) / 100.0;
+	for (i = 0; i < l; i++)
+		printf("*");
+	for (; i < w; i++)
+		printf(" ");
+	printf("|%4d%%", (int)perc);
+
+	fflush(stdout);
+}
+
+static int
+fetch_fid(int fid, const char *path)
+{
+	struct progress p = {0};
+	struct np_stat st;
+	size_t r;
+	int fd;
+	char buf[BUFSIZ];
+
+	do_stat(fid, &st);
+	do_open(fid, KOREAD);
+
+	if ((fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0644)) == -1) {
+		warn("can't open %s", path);
+		return -1;
+	}
+
+	p.max = st.length;
+	for (;;) {
+		size_t siz, off;
+		ssize_t nw;
+
+		r = do_read(fid, p.done, sizeof(buf), buf);
+		if (r == 0)
+			break;
+
+		siz = sizeof(buf);
+		for (off = 0; off < siz; off += nw)
+			if ((nw = write(fd, buf + off, siz - off)) == 0 ||
+			    nw == -1)
+				err(1, "write");
+
+		p.done += r;
+		draw_progress(path, &p);
+
+#if 0
+		/* throttle, for debugging purpose */
+		{
+			struct timespec ts = { 0, 500000000 };
+			nanosleep(&ts, NULL);
+		}
+#endif
+	}
+
+	putchar('\n');
+
+	close(fd);
+	do_clunk(fid);
+	return 0;
+}
+
+static void
+do_tls_connect(const char *host, const char *port)
+{
+	int handshake;
+
+	if ((tlsconf = tls_config_new()) == NULL)
+		fatalx("tls_config_new");
+	tls_config_insecure_noverifycert(tlsconf);
+	tls_config_insecure_noverifyname(tlsconf);
+	if (tls_config_set_keypair_file(tlsconf, crtpath, keypath) == -1)
+		fatalx("can't load certs (%s, %s)", crtpath, keypath);
+
+	if ((ctx = tls_client()) == NULL)
+		fatal("tls_client");
+	if (tls_configure(ctx, tlsconf) == -1)
+		fatalx("tls_configure: %s", tls_error(ctx));
+
+	if (tls_connect(ctx, host, port) == -1)
+		fatalx("can't connect to %s:%s: %s", host, port,
+		    tls_error(ctx));
+
+	for (handshake = 0; !handshake;) {
+		switch (tls_handshake(ctx)) {
+		case -1:
+			fatalx("tls_handshake: %s", tls_error(ctx));
+		case 0:
+			handshake = 1;
+			break;
+		}
+	}
+}
+
+static void
+do_ctxt_connect(const char *host, const char *port)
+{
+	struct addrinfo hints, *res, *res0;
+	int error, saved_errno;
+	const char *cause = NULL;
+
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_family = AF_UNSPEC;
+	hints.ai_socktype = SOCK_STREAM;
+	error = getaddrinfo(host, port, &hints, &res0);
+	if (error)
+		errx(1, "%s", gai_strerror(error));
+
+	sock = -1;
+	for (res = res0; res != NULL; res = res->ai_next) {
+		sock = socket(res->ai_family, res->ai_socktype,
+		    res->ai_protocol);
+		if (sock == -1) {
+			cause = "socket";
+			continue;
+		}
+
+		if (connect(sock, res->ai_addr, res->ai_addrlen) == -1) {
+			cause = "connect";
+			saved_errno = errno;
+			close(sock);
+			errno = saved_errno;
+			sock = -1;
+			continue;
+		}
+
+		break;
+	}
+
+	if (sock == -1)
+		err(1, "%s", cause);
+	freeaddrinfo(res0);
+}
+
+static void
+do_connect(const char *connspec, const char *path)
+{
+	char *host, *colon;
+	const char *port;
+
+	host = xstrdup(connspec);
+	if ((colon = strchr(host, ':')) != NULL) {
+		*colon = '\0';
+		port = ++colon;
+	} else
+		port = "1337";
+
+	printf("connecting to %s:%s...", host, port);
+	fflush(stdout);
+
+	if (tls)
+		do_tls_connect(host, port);
+	else
+		do_ctxt_connect(host, port);
+
+	printf(" done!\n");
+
+	do_version();
+	do_attach(path);
+
+	free(host);
+}
+
+static void
+cmd_bell(int argc, const char **argv)
+{
+	if (argc == 0) {
+		bell = !bell;
+		if (bell)
+			puts("bell mode enabled");
+		else
+			puts("bell mode disabled");
+		return;
+	}
+
+	if (argc != 1)
+		goto usage;
+
+	if (!strcmp(*argv, "on")) {
+		bell = 1;
+		puts("bell mode enabled");
+		return;
+	}
+
+	if (!strcmp(*argv, "off")) {
+		bell = 0;
+		puts("bell mode disabled");
+		return;
+	}
+
+usage:
+	printf("bell [on | off]\n");
+}
+
+static void
+cmd_bye(int argc, const char **argv)
+{
+	log_warnx("bye\n");
+	exit(0);
+}
+
+static void
+cmd_cd(int argc, const char **argv)
+{
+	struct qid qid;
+	int nfid;
+
+	if (argc != 1) {
+		printf("usage: cd remote-path\n");
+		return;
+	}
+
+	nfid = pwdfid+1;
+	if (walk_path(pwdfid, nfid, argv[0], &qid) == -1 ||
+	    !(qid.type & QTDIR)) {
+		printf("can't cd %s\n", argv[0]);
+		do_clunk(nfid);
+	} else {
+		do_clunk(pwdfid);
+		pwdfid = nfid;
+	}
+}
+
+static void
+cmd_get(int argc, const char **argv)
+{
+	struct qid qid;
+	const char *l;
+	int nfid;
+
+	if (argc != 1 && argc != 2) {
+		printf("usage: get remote-file [local-file]\n");
+		return;
+	}
+
+	if (argc == 2)
+		l = argv[1];
+	else if ((l = strrchr(argv[0], '/')) != NULL)
+		l++; /* skip / */
+	else
+		l = argv[1];
+
+	nfid = pwdfid+1;
+	if (walk_path(pwdfid, nfid, argv[0], &qid) == -1) {
+		printf("can't fetch %s\n", argv[0]);
+		return;
+	}
+
+	if (qid.type != 0) {
+		printf("can't fetch %s\n", argv[0]);
+		do_clunk(nfid);
+		return;
+	}
+
+	fetch_fid(nfid, l);
+}
+
+static void
+cmd_lcd(int argc, const char **argv)
+{
+	const char *dir;
+
+	if (argc > 1) {
+		printf("lcd takes only one argument\n");
+		return;
+	}
+
+	if (argc == 1)
+		dir = *argv;
+
+	if (argc == 0 && (dir = getenv("HOME")) == NULL) {
+		printf("HOME is not defined\n");
+		return;
+	}
+
+	if (chdir(dir) == -1)
+		printf("cd: %s: %s\n", dir, strerror(errno));
+}
+
+static void
+cmd_lpwd(int argc, const char **argv)
+{
+	char path[PATH_MAX];
+
+	if (getcwd(path, sizeof(path)) == NULL) {
+		printf("lpwd: %s\n", strerror(errno));
+		return;
+	}
+
+	printf("%s\n", path);
+}
+
+static void
+cmd_ls(int argc, const char **argv)
+{
+	struct np_stat st;
+	uint64_t off = 0;
+	uint32_t len;
+	char fmt[FMT_SCALED_STRSIZE];
+
+	if (argc != 0) {
+		printf("ls don't take arguments (yet)\n");
+		return;
+	}
+
+	dup_fid(pwdfid, 1);
+	do_open(1, KOREAD);
+
+	evbuffer_drain(dirbuf, EVBUFFER_LENGTH(dirbuf));
+
+	for (;;) {
+		tread(1, off, BUFSIZ);
+		do_send();
+		recv_msg();
+		expect2(Rread, iota_tag);
+
+		len = np_read32(buf);
+		if (len == 0)
+			break;
+
+		evbuffer_add_buffer(dirbuf, buf);
+		off += len;
+
+		ASSERT_EMPTYBUF();
+	}
+
+	while (EVBUFFER_LENGTH(dirbuf) != 0) {
+		if (np_read_stat(dirbuf, &st) == -1)
+			errx(1, "invalid stat struct read");
+
+		if (fmt_scaled(st.length, fmt) == -1)
+			strlcpy(fmt, "xxx", sizeof(fmt));
+
+		printf("%4s %8s %s\n", pp_qid_type(st.qid.type), fmt, st.name);
+
+		free(st.name);
+		free(st.uid);
+		free(st.gid);
+		free(st.muid);
+	}
+
+	do_clunk(1);
+}
+
+static void
+cmd_verbose(int argc, const char **argv)
+{
+	if (argc == 0) {
+		log_setverbose(!log_getverbose());
+		if (log_getverbose())
+			puts("verbose mode enabled");
+		else
+			puts("verbose mode disabled");
+		return;
+	}
+
+	if (argc != 1)
+		goto usage;
+
+	if (!strcmp(*argv, "on")) {
+		log_setverbose(1);
+		puts("verbose mode enabled");
+		return;
+	}
+
+	if (!strcmp(*argv, "off")) {
+		log_setverbose(0);
+		puts("verbose mode disabled");
+		return;
+	}
+
+usage:
+	printf("verbose [on | off]\n");
+}
+
+static void
+excmd(int argc, const char **argv)
+{
+	struct cmd {
+		const char	*name;
+		void		(*fn)(int, const char **);
+	} cmds[] = {
+		{"bell",	cmd_bell},
+		{"bye",		cmd_bye},
+		{"cd",		cmd_cd},
+		{"get",		cmd_get},
+		{"lcd",		cmd_lcd},
+		{"lpwd",	cmd_lpwd},
+		{"ls",		cmd_ls},
+		{"quit",	cmd_bye},
+		{"verbose",	cmd_verbose},
+	};
+	size_t i;
+
+	if (argc == 0)
+		return;
+	for (i = 0; i < nitems(cmds); ++i) {
+		if (!strcmp(cmds[i].name, *argv)) {
+			cmds[i].fn(argc-1, argv+1);
+			return;
+		}
+	}
+
+	log_warnx("unknown command %s", *argv);
+}
+
+int
+main(int argc, char **argv)
+{
+	int	ch;
+
+	log_init(1, LOG_DAEMON);
+	log_setverbose(0);
+	log_procinit(getprogname());
+
+	while ((ch = getopt(argc, argv, "C:cK:")) != -1) {
+		switch (ch) {
+		case 'C':
+			crtpath = optarg;
+			break;
+		case 'c':
+			tls = 1;
+			break;
+		case 'K':
+			keypath = optarg;
+			break;
+		default:
+			usage(1);
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (argc == 0)
+		usage(1);
+
+	if (isatty(1)) {
+		tty_p = 1;
+		resized = 1;
+		signal(SIGWINCH, tty_resized);
+	}
+
+	if ((evb = evbuffer_new()) == NULL)
+		fatal("evbuffer_new");
+
+	if ((buf = evbuffer_new()) == NULL)
+		fatal("evbuffer_new");
+
+	if ((dirbuf = evbuffer_new()) == NULL)
+		fatal("evbuferr_new");
+
+	do_connect(argv[0], argv[1]);
+
+	for (;;) {
+		int argc = 0;
+		char *line, *argv[16] = {0}, **ap;
+
+		if ((line = read_line("kamiftp> ")) == NULL)
+			break;
+
+		for (argc = 0, ap = argv; ap < &argv[15] &&
+		    (*ap = strsep(&line, " \t")) != NULL;) {
+			if (**ap != '\0')
+				ap++, argc++;
+		}
+		excmd(argc, (const char **)argv);
+
+		if (bell) {
+			printf("\a");
+			fflush(stdout);
+		}
+
+		free(line);
+	}
+
+	printf("\n");
+}
blob - /dev/null
blob + bbb82ddbd18431c0c4cc0cc05d9e9273e5bf4d26 (mode 644)
--- /dev/null
+++ kamiftp/kamiftp.1
@@ -0,0 +1,80 @@
+.\" 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.
+.\"
+.Dd $Mdocdate: December 14 2021 $
+.Dt KAMIFTP 1
+.Os
+.Sh NAME
+.Nm kamiftp
+.Nd 9p client
+.Sh SYNOPSIS
+.Nm
+.Op Fl c
+.Op Fl C Ar cert
+.Op Fl K Ar key
+.Ar host Op Ar path
+.Sh DESCRIPTION
+.Nm
+is a
+.Xr 9p 7
+client.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl c
+Use TLS for the connection.
+.Fl K
+and
+.Fl C
+are mandatory if
+.Fl c
+is used.
+.It Fl C Ar certificate
+Specify the path to the client
+.Ar certificate
+to be use during the TLS handsahke.
+.It Fl K Ar key
+Specify the path to the client certificate
+.Ar key
+to be used during the TLS handshake.
+.El
+.Pp
+The following commands are recognized by
+.Nm :
+.Bl -tag -width Ds
+.It Ic bell Oo Cm on | off Oc
+Request terminal to sound a bell after each command.
+Without arguments toggle the current state.
+.It Ic bye
+Terminate the session.
+Synomym of
+.Ic quit .
+.It Ic ls
+List the file in the current directory
+.It Ic quit
+Terminate the session.
+Synomym of
+.Ic bye .
+.It Ic verbose Oo Cm on | off Oc
+Print verbose information.
+Without arguments toggle the current state.
+.El
+.Sh SEE ALSO
+.Xr 9p 7 ,
+.Xr kamid 8
+.Sh AUTHORS
+The
+.Nm
+utility was written by
+.An Omar Polo Aq Mt op@omarpolo.com .
blob - 70d2ea27dc6aebc565bc7ae98b4afe074d88045f (mode 644)
blob + /dev/null
--- compat/asprintf.c
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (c) 2006 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 <stdlib.h>
-
-#include "compat.h"
-
-int
-asprintf(char **ret, const char *fmt, ...)
-{
-	va_list	ap;
-	int	n;
-
-	va_start(ap, fmt);
-	n = vasprintf(ret, fmt, ap);
-	va_end(ap);
-
-	return (n);
-}
-
-int
-vasprintf(char **ret, const char *fmt, va_list ap)
-{
-	int	 n;
-	va_list  ap2;
-
-	va_copy(ap2, ap);
-
-	if ((n = vsnprintf(NULL, 0, fmt, ap)) < 0)
-		goto error;
-
-	if ((*ret = malloc(n + 1)) == NULL)
-		goto error;
-	if ((n = vsnprintf(*ret, n + 1, fmt, ap2)) < 0) {
-		free(*ret);
-		goto error;
-	}
-	va_end(ap2);
-
-	return (n);
-
-error:
-	va_end(ap2);
-	*ret = NULL;
-	return (-1);
-}
blob - 244909c09b1829522fc3429f5e207de7edd34bc2 (mode 644)
blob + /dev/null
--- compat/err.c
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * 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>
-
-#include "compat.h"
-
-static void vwarn(const char*, va_list);
-static void vwarnx(const char*, va_list);
-
-static void
-vwarn(const char *fmt, va_list ap)
-{
-	fprintf(stderr, "%s: ", getprogname());
-	vfprintf(stderr, fmt, ap);
-	fprintf(stderr, ": %s\n", strerror(errno));
-}
-
-static void
-vwarnx(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(fmt, ap);
-	va_end(ap);
-	exit(ret);
-}
-
-void
-errx(int ret, const char *fmt, ...)
-{
-	va_list	ap;
-
-	va_start(ap, fmt);
-	vwarnx(fmt, ap);
-	va_end(ap);
-	exit(ret);
-}
-
-void
-warn(const char *fmt, ...)
-{
-	va_list	ap;
-
-	va_start(ap, fmt);
-	vwarn(fmt, ap);
-	va_end(ap);
-}
-
-void
-warnx(const char *fmt, ...)
-{
-	va_list	ap;
-
-	va_start(ap, fmt);
-	vwarnx(fmt, ap);
-	va_end(ap);
-}
blob - 1b2d5f59da6911eb007cd5a1fae0d58fc2fce889 (mode 644)
blob + /dev/null
--- compat/fmt_scaled.c
+++ /dev/null
@@ -1,159 +0,0 @@
-/*	$OpenBSD: fmt_scaled.c,v 1.19 2020/10/12 22:08:34 deraadt Exp $	*/
-
-/*
- * Copyright (c) 2001, 2002, 2003 Ian F. Darwin.  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.
- */
-
-/*
- * fmt_scaled: Format numbers scaled for human comprehension
- * scan_scaled: Scan numbers in this format.
- *
- * "Human-readable" output uses 4 digits max, and puts a unit suffix at
- * the end.  Makes output compact and easy-to-read esp. on huge disks.
- * Formatting code was originally in OpenBSD "df", converted to library routine.
- * Scanning code written for OpenBSD libutil.
- */
-
-#include "compat.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <string.h>
-#include <ctype.h>
-#include <limits.h>
-
-typedef enum {
-	NONE = 0, KILO = 1, MEGA = 2, GIGA = 3, TERA = 4, PETA = 5, EXA = 6
-} unit_type;
-
-/* These three arrays MUST be in sync!  XXX make a struct */
-static const unit_type units[] = { NONE, KILO, MEGA, GIGA, TERA, PETA, EXA };
-static const char scale_chars[] = "BKMGTPE";
-static const long long scale_factors[] = {
-	1LL,
-	1024LL,
-	1024LL*1024,
-	1024LL*1024*1024,
-	1024LL*1024*1024*1024,
-	1024LL*1024*1024*1024*1024,
-	1024LL*1024*1024*1024*1024*1024,
-};
-#define	SCALE_LENGTH (sizeof(units)/sizeof(units[0]))
-
-#define MAX_DIGITS (SCALE_LENGTH * 3)	/* XXX strlen(sprintf("%lld", -1)? */
-
-/* Format the given "number" into human-readable form in "result".
- * Result must point to an allocated buffer of length FMT_SCALED_STRSIZE.
- * Return 0 on success, -1 and errno set if error.
- */
-int
-fmt_scaled(long long number, char *result)
-{
-	long long abval, fract = 0;
-	unsigned int i;
-	unit_type unit = NONE;
-
-	/* Not every negative long long has a positive representation. */
-	if (number == LLONG_MIN) {
-		errno = ERANGE;
-		return -1;
-	}
-
-	abval = llabs(number);
-
-	/* Also check for numbers that are just too darned big to format. */
-	if (abval / 1024 >= scale_factors[SCALE_LENGTH-1]) {
-		errno = ERANGE;
-		return -1;
-	}
-
-	/* scale whole part; get unscaled fraction */
-	for (i = 0; i < SCALE_LENGTH; i++) {
-		if (abval/1024 < scale_factors[i]) {
-			unit = units[i];
-			fract = (i == 0) ? 0 : abval % scale_factors[i];
-			number /= scale_factors[i];
-			if (i > 0)
-				fract /= scale_factors[i - 1];
-			break;
-		}
-	}
-
-	fract = (10 * fract + 512) / 1024;
-	/* if the result would be >= 10, round main number */
-	if (fract >= 10) {
-		if (number >= 0)
-			number++;
-		else
-			number--;
-		fract = 0;
-	} else if (fract < 0) {
-		/* shouldn't happen */
-		fract = 0;
-	}
-
-	if (number == 0)
-		strlcpy(result, "0B", FMT_SCALED_STRSIZE);
-	else if (unit == NONE || number >= 100 || number <= -100) {
-		if (fract >= 5) {
-			if (number >= 0)
-				number++;
-			else
-				number--;
-		}
-		(void)snprintf(result, FMT_SCALED_STRSIZE, "%lld%c",
-			number, scale_chars[unit]);
-	} else
-		(void)snprintf(result, FMT_SCALED_STRSIZE, "%lld.%1lld%c",
-			number, fract, scale_chars[unit]);
-
-	return 0;
-}
-
-#ifdef	MAIN
-/*
- * This is the original version of the program in the man page.
- * Copy-and-paste whatever you need from it.
- */
-int
-main(int argc, char **argv)
-{
-	char *cinput = "1.5K", buf[FMT_SCALED_STRSIZE];
-	long long ninput = 10483892, result;
-
-	if (scan_scaled(cinput, &result) == 0)
-		printf("\"%s\" -> %lld\n", cinput, result);
-	else
-		perror(cinput);
-
-	if (fmt_scaled(ninput, buf) == 0)
-		printf("%lld -> \"%s\"\n", ninput, buf);
-	else
-		fprintf(stderr, "%lld invalid (%s)\n", ninput, strerror(errno));
-
-	return 0;
-}
-#endif
blob - 9929f01e8cc1fccffb64b4699ec6fdf5ad566446 (mode 644)
blob + /dev/null
--- compat/freezero.c
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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 "../compat.h"
-
-#include <stdlib.h>
-#include <string.h>
-
-void
-freezero(void *ptr, size_t len)
-{
-	if (ptr == NULL)
-		return;
-
-	memset(ptr, 0, len);
-	free(ptr);
-}
blob - 1d75ae2114b24059d3b4f0fe18e8d925db430b5d (mode 644)
blob + /dev/null
--- compat/getdtablecount.c
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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 "compat.h"
-
-int
-getdtablecount(void)
-{
-	return 0;
-}
blob - a9a8e9d52cba2c1315734677e41e7c83d6399f4b (mode 644)
blob + /dev/null
--- compat/getdtablesize.c
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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 "compat.h"
-
-#include <unistd.h>
-
-int
-getdtablesize(void)
-{
-	return sysconf(_SC_OPEN_MAX);
-}
blob - 1b50f0c49503e60bd7ce9207d9cc07e7686673c2 (mode 644)
blob + /dev/null
--- compat/getprogname.c
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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 "compat.h"
-
-#ifdef HAVE_PROGRAM_INVOCATION_SHORT_NAME
-
-#include <errno.h>
-
-extern char *program_invocation_short_name;
-
-const char *
-getprogname(void)
-{
-	return program_invocation_short_name;
-}
-
-#else
-
-const char *
-getprogname(void)
-{
-	return "kamid";
-}
-
-#endif
blob - 7bd827d6354db030b1fbffc9a365200e017a557e (mode 644)
blob + /dev/null
--- compat/imsg-buffer.c
+++ /dev/null
@@ -1,310 +0,0 @@
-/*	$OpenBSD: imsg-buffer.c,v 1.12 2019/01/20 02:50:03 bcook 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 "compat.h"
-
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/uio.h>
-
-#include <limits.h>
-#include <errno.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include "imsg.h"
-
-static int	ibuf_realloc(struct ibuf *, size_t);
-static void	ibuf_enqueue(struct msgbuf *, struct ibuf *);
-static void	ibuf_dequeue(struct msgbuf *, struct ibuf *);
-
-struct ibuf *
-ibuf_open(size_t len)
-{
-	struct ibuf	*buf;
-
-	if ((buf = calloc(1, sizeof(struct ibuf))) == NULL)
-		return (NULL);
-	if ((buf->buf = malloc(len)) == NULL) {
-		free(buf);
-		return (NULL);
-	}
-	buf->size = buf->max = len;
-	buf->fd = -1;
-
-	return (buf);
-}
-
-struct ibuf *
-ibuf_dynamic(size_t len, size_t max)
-{
-	struct ibuf	*buf;
-
-	if (max < len)
-		return (NULL);
-
-	if ((buf = ibuf_open(len)) == NULL)
-		return (NULL);
-
-	if (max > 0)
-		buf->max = max;
-
-	return (buf);
-}
-
-static int
-ibuf_realloc(struct ibuf *buf, size_t len)
-{
-	unsigned char	*b;
-
-	/* on static buffers max is eq size and so the following fails */
-	if (buf->wpos + len > buf->max) {
-		errno = ERANGE;
-		return (-1);
-	}
-
-	b = recallocarray(buf->buf, buf->size, buf->wpos + len, 1);
-	if (b == NULL)
-		return (-1);
-	buf->buf = b;
-	buf->size = buf->wpos + len;
-
-	return (0);
-}
-
-int
-ibuf_add(struct ibuf *buf, const void *data, size_t len)
-{
-	if (buf->wpos + len > buf->size)
-		if (ibuf_realloc(buf, len) == -1)
-			return (-1);
-
-	memcpy(buf->buf + buf->wpos, data, len);
-	buf->wpos += len;
-	return (0);
-}
-
-void *
-ibuf_reserve(struct ibuf *buf, size_t len)
-{
-	void	*b;
-
-	if (buf->wpos + len > buf->size)
-		if (ibuf_realloc(buf, len) == -1)
-			return (NULL);
-
-	b = buf->buf + buf->wpos;
-	buf->wpos += len;
-	return (b);
-}
-
-void *
-ibuf_seek(struct ibuf *buf, size_t pos, size_t len)
-{
-	/* only allowed to seek in already written parts */
-	if (pos + len > buf->wpos)
-		return (NULL);
-
-	return (buf->buf + pos);
-}
-
-size_t
-ibuf_size(struct ibuf *buf)
-{
-	return (buf->wpos);
-}
-
-size_t
-ibuf_left(struct ibuf *buf)
-{
-	return (buf->max - buf->wpos);
-}
-
-void
-ibuf_close(struct msgbuf *msgbuf, struct ibuf *buf)
-{
-	ibuf_enqueue(msgbuf, buf);
-}
-
-int
-ibuf_write(struct msgbuf *msgbuf)
-{
-	struct iovec	 iov[IOV_MAX];
-	struct ibuf	*buf;
-	unsigned int	 i = 0;
-	ssize_t	n;
-
-	memset(&iov, 0, sizeof(iov));
-	TAILQ_FOREACH(buf, &msgbuf->bufs, entry) {
-		if (i >= IOV_MAX)
-			break;
-		iov[i].iov_base = buf->buf + buf->rpos;
-		iov[i].iov_len = buf->wpos - buf->rpos;
-		i++;
-	}
-
-again:
-	if ((n = writev(msgbuf->fd, iov, i)) == -1) {
-		if (errno == EINTR)
-			goto again;
-		if (errno == ENOBUFS)
-			errno = EAGAIN;
-		return (-1);
-	}
-
-	if (n == 0) {			/* connection closed */
-		errno = 0;
-		return (0);
-	}
-
-	msgbuf_drain(msgbuf, n);
-
-	return (1);
-}
-
-void
-ibuf_free(struct ibuf *buf)
-{
-	if (buf == NULL)
-		return;
-	freezero(buf->buf, buf->size);
-	free(buf);
-}
-
-void
-msgbuf_init(struct msgbuf *msgbuf)
-{
-	msgbuf->queued = 0;
-	msgbuf->fd = -1;
-	TAILQ_INIT(&msgbuf->bufs);
-}
-
-void
-msgbuf_drain(struct msgbuf *msgbuf, size_t n)
-{
-	struct ibuf	*buf, *next;
-
-	for (buf = TAILQ_FIRST(&msgbuf->bufs); buf != NULL && n > 0;
-	    buf = next) {
-		next = TAILQ_NEXT(buf, entry);
-		if (buf->rpos + n >= buf->wpos) {
-			n -= buf->wpos - buf->rpos;
-			ibuf_dequeue(msgbuf, buf);
-		} else {
-			buf->rpos += n;
-			n = 0;
-		}
-	}
-}
-
-void
-msgbuf_clear(struct msgbuf *msgbuf)
-{
-	struct ibuf	*buf;
-
-	while ((buf = TAILQ_FIRST(&msgbuf->bufs)) != NULL)
-		ibuf_dequeue(msgbuf, buf);
-}
-
-int
-msgbuf_write(struct msgbuf *msgbuf)
-{
-	struct iovec	 iov[IOV_MAX];
-	struct ibuf	*buf;
-	unsigned int	 i = 0;
-	ssize_t		 n;
-	struct msghdr	 msg;
-	struct cmsghdr	*cmsg;
-	union {
-		struct cmsghdr	hdr;
-		char		buf[CMSG_SPACE(sizeof(int))];
-	} cmsgbuf;
-
-	memset(&iov, 0, sizeof(iov));
-	memset(&msg, 0, sizeof(msg));
-	memset(&cmsgbuf, 0, sizeof(cmsgbuf));
-	TAILQ_FOREACH(buf, &msgbuf->bufs, entry) {
-		if (i >= IOV_MAX)
-			break;
-		iov[i].iov_base = buf->buf + buf->rpos;
-		iov[i].iov_len = buf->wpos - buf->rpos;
-		i++;
-		if (buf->fd != -1)
-			break;
-	}
-
-	msg.msg_iov = iov;
-	msg.msg_iovlen = i;
-
-	if (buf != NULL && buf->fd != -1) {
-		msg.msg_control = (caddr_t)&cmsgbuf.buf;
-		msg.msg_controllen = sizeof(cmsgbuf.buf);
-		cmsg = CMSG_FIRSTHDR(&msg);
-		cmsg->cmsg_len = CMSG_LEN(sizeof(int));
-		cmsg->cmsg_level = SOL_SOCKET;
-		cmsg->cmsg_type = SCM_RIGHTS;
-		*(int *)CMSG_DATA(cmsg) = buf->fd;
-	}
-
-again:
-	if ((n = sendmsg(msgbuf->fd, &msg, 0)) == -1) {
-		if (errno == EINTR)
-			goto again;
-		if (errno == ENOBUFS)
-			errno = EAGAIN;
-		return (-1);
-	}
-
-	if (n == 0) {			/* connection closed */
-		errno = 0;
-		return (0);
-	}
-
-	/*
-	 * assumption: fd got sent if sendmsg sent anything
-	 * this works because fds are passed one at a time
-	 */
-	if (buf != NULL && buf->fd != -1) {
-		close(buf->fd);
-		buf->fd = -1;
-	}
-
-	msgbuf_drain(msgbuf, n);
-
-	return (1);
-}
-
-static void
-ibuf_enqueue(struct msgbuf *msgbuf, struct ibuf *buf)
-{
-	TAILQ_INSERT_TAIL(&msgbuf->bufs, buf, entry);
-	msgbuf->queued++;
-}
-
-static void
-ibuf_dequeue(struct msgbuf *msgbuf, struct ibuf *buf)
-{
-	TAILQ_REMOVE(&msgbuf->bufs, buf, entry);
-
-	if (buf->fd != -1)
-		close(buf->fd);
-
-	msgbuf->queued--;
-	ibuf_free(buf);
-}
blob - 6c746751450bd523a4fb6736a4347900fd01d5bb (mode 644)
blob + /dev/null
--- compat/imsg.c
+++ /dev/null
@@ -1,303 +0,0 @@
-/*	$OpenBSD: imsg.c,v 1.16 2017/12/14 09:27:44 kettenis 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 "compat.h"
-
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/uio.h>
-
-#include <errno.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include "imsg.h"
-
-int	 imsg_fd_overhead = 0;
-
-static int	 imsg_get_fd(struct imsgbuf *);
-
-void
-imsg_init(struct imsgbuf *ibuf, int fd)
-{
-	msgbuf_init(&ibuf->w);
-	memset(&ibuf->r, 0, sizeof(ibuf->r));
-	ibuf->fd = fd;
-	ibuf->w.fd = fd;
-	ibuf->pid = getpid();
-	TAILQ_INIT(&ibuf->fds);
-}
-
-ssize_t
-imsg_read(struct imsgbuf *ibuf)
-{
-	struct msghdr		 msg;
-	struct cmsghdr		*cmsg;
-	union {
-		struct cmsghdr hdr;
-		char	buf[CMSG_SPACE(sizeof(int) * 1)];
-	} cmsgbuf;
-	struct iovec		 iov;
-	ssize_t			 n = -1;
-	int			 fd;
-	struct imsg_fd		*ifd;
-
-	memset(&msg, 0, sizeof(msg));
-	memset(&cmsgbuf, 0, sizeof(cmsgbuf));
-
-	iov.iov_base = ibuf->r.buf + ibuf->r.wpos;
-	iov.iov_len = sizeof(ibuf->r.buf) - ibuf->r.wpos;
-	msg.msg_iov = &iov;
-	msg.msg_iovlen = 1;
-	msg.msg_control = &cmsgbuf.buf;
-	msg.msg_controllen = sizeof(cmsgbuf.buf);
-
-	if ((ifd = calloc(1, sizeof(struct imsg_fd))) == NULL)
-		return (-1);
-
-again:
-	if (getdtablecount() + imsg_fd_overhead +
-	    (int)((CMSG_SPACE(sizeof(int))-CMSG_SPACE(0))/sizeof(int))
-	    >= getdtablesize()) {
-		errno = EAGAIN;
-		free(ifd);
-		return (-1);
-	}
-
-	if ((n = recvmsg(ibuf->fd, &msg, 0)) == -1) {
-		if (errno == EINTR)
-			goto again;
-		goto fail;
-	}
-
-	ibuf->r.wpos += n;
-
-	for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
-	    cmsg = CMSG_NXTHDR(&msg, cmsg)) {
-		if (cmsg->cmsg_level == SOL_SOCKET &&
-		    cmsg->cmsg_type == SCM_RIGHTS) {
-			int i;
-			int j;
-
-			/*
-			 * We only accept one file descriptor.  Due to C
-			 * padding rules, our control buffer might contain
-			 * more than one fd, and we must close them.
-			 */
-			j = ((char *)cmsg + cmsg->cmsg_len -
-			    (char *)CMSG_DATA(cmsg)) / sizeof(int);
-			for (i = 0; i < j; i++) {
-				fd = ((int *)CMSG_DATA(cmsg))[i];
-				if (ifd != NULL) {
-					ifd->fd = fd;
-					TAILQ_INSERT_TAIL(&ibuf->fds, ifd,
-					    entry);
-					ifd = NULL;
-				} else
-					close(fd);
-			}
-		}
-		/* we do not handle other ctl data level */
-	}
-
-fail:
-	free(ifd);
-	return (n);
-}
-
-ssize_t
-imsg_get(struct imsgbuf *ibuf, struct imsg *imsg)
-{
-	size_t			 av, left, datalen;
-
-	av = ibuf->r.wpos;
-
-	if (IMSG_HEADER_SIZE > av)
-		return (0);
-
-	memcpy(&imsg->hdr, ibuf->r.buf, sizeof(imsg->hdr));
-	if (imsg->hdr.len < IMSG_HEADER_SIZE ||
-	    imsg->hdr.len > MAX_IMSGSIZE) {
-		errno = ERANGE;
-		return (-1);
-	}
-	if (imsg->hdr.len > av)
-		return (0);
-	datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
-	ibuf->r.rptr = ibuf->r.buf + IMSG_HEADER_SIZE;
-	if (datalen == 0)
-		imsg->data = NULL;
-	else if ((imsg->data = malloc(datalen)) == NULL)
-		return (-1);
-
-	if (imsg->hdr.flags & IMSGF_HASFD)
-		imsg->fd = imsg_get_fd(ibuf);
-	else
-		imsg->fd = -1;
-
-	memcpy(imsg->data, ibuf->r.rptr, datalen);
-
-	if (imsg->hdr.len < av) {
-		left = av - imsg->hdr.len;
-		memmove(&ibuf->r.buf, ibuf->r.buf + imsg->hdr.len, left);
-		ibuf->r.wpos = left;
-	} else
-		ibuf->r.wpos = 0;
-
-	return (datalen + IMSG_HEADER_SIZE);
-}
-
-int
-imsg_compose(struct imsgbuf *ibuf, uint32_t type, uint32_t peerid, pid_t pid,
-    int fd, const void *data, uint16_t datalen)
-{
-	struct ibuf	*wbuf;
-
-	if ((wbuf = imsg_create(ibuf, type, peerid, pid, datalen)) == NULL)
-		return (-1);
-
-	if (imsg_add(wbuf, data, datalen) == -1)
-		return (-1);
-
-	wbuf->fd = fd;
-
-	imsg_close(ibuf, wbuf);
-
-	return (1);
-}
-
-int
-imsg_composev(struct imsgbuf *ibuf, uint32_t type, uint32_t peerid, pid_t pid,
-    int fd, const struct iovec *iov, int iovcnt)
-{
-	struct ibuf	*wbuf;
-	int		 i, datalen = 0;
-
-	for (i = 0; i < iovcnt; i++)
-		datalen += iov[i].iov_len;
-
-	if ((wbuf = imsg_create(ibuf, type, peerid, pid, datalen)) == NULL)
-		return (-1);
-
-	for (i = 0; i < iovcnt; i++)
-		if (imsg_add(wbuf, iov[i].iov_base, iov[i].iov_len) == -1)
-			return (-1);
-
-	wbuf->fd = fd;
-
-	imsg_close(ibuf, wbuf);
-
-	return (1);
-}
-
-/* ARGSUSED */
-struct ibuf *
-imsg_create(struct imsgbuf *ibuf, uint32_t type, uint32_t peerid, pid_t pid,
-    uint16_t datalen)
-{
-	struct ibuf	*wbuf;
-	struct imsg_hdr	 hdr;
-
-	datalen += IMSG_HEADER_SIZE;
-	if (datalen > MAX_IMSGSIZE) {
-		errno = ERANGE;
-		return (NULL);
-	}
-
-	hdr.type = type;
-	hdr.flags = 0;
-	hdr.peerid = peerid;
-	if ((hdr.pid = pid) == 0)
-		hdr.pid = ibuf->pid;
-	if ((wbuf = ibuf_dynamic(datalen, MAX_IMSGSIZE)) == NULL) {
-		return (NULL);
-	}
-	if (imsg_add(wbuf, &hdr, sizeof(hdr)) == -1)
-		return (NULL);
-
-	return (wbuf);
-}
-
-int
-imsg_add(struct ibuf *msg, const void *data, uint16_t datalen)
-{
-	if (datalen)
-		if (ibuf_add(msg, data, datalen) == -1) {
-			ibuf_free(msg);
-			return (-1);
-		}
-	return (datalen);
-}
-
-void
-imsg_close(struct imsgbuf *ibuf, struct ibuf *msg)
-{
-	struct imsg_hdr	*hdr;
-
-	hdr = (struct imsg_hdr *)msg->buf;
-
-	hdr->flags &= ~IMSGF_HASFD;
-	if (msg->fd != -1)
-		hdr->flags |= IMSGF_HASFD;
-
-	hdr->len = (uint16_t)msg->wpos;
-
-	ibuf_close(&ibuf->w, msg);
-}
-
-void
-imsg_free(struct imsg *imsg)
-{
-	freezero(imsg->data, imsg->hdr.len - IMSG_HEADER_SIZE);
-}
-
-static int
-imsg_get_fd(struct imsgbuf *ibuf)
-{
-	int		 fd;
-	struct imsg_fd	*ifd;
-
-	if ((ifd = TAILQ_FIRST(&ibuf->fds)) == NULL)
-		return (-1);
-
-	fd = ifd->fd;
-	TAILQ_REMOVE(&ibuf->fds, ifd, entry);
-	free(ifd);
-
-	return (fd);
-}
-
-int
-imsg_flush(struct imsgbuf *ibuf)
-{
-	while (ibuf->w.queued)
-		if (msgbuf_write(&ibuf->w) <= 0)
-			return (-1);
-	return (0);
-}
-
-void
-imsg_clear(struct imsgbuf *ibuf)
-{
-	int	fd;
-
-	msgbuf_clear(&ibuf->w);
-	while ((fd = imsg_get_fd(ibuf)) != -1)
-		close(fd);
-}
blob - 5b092cfcfd53b5b09a2d5932010f421d6795e0aa (mode 644)
blob + /dev/null
--- compat/imsg.h
+++ /dev/null
@@ -1,113 +0,0 @@
-/*	$OpenBSD: imsg.h,v 1.5 2019/01/20 02:50:03 bcook Exp $	*/
-
-/*
- * Copyright (c) 2006, 2007 Pierre-Yves Ritschard <pyr@openbsd.org>
- * Copyright (c) 2006, 2007, 2008 Reyk Floeter <reyk@openbsd.org>
- * 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 _IMSG_H_
-#define _IMSG_H_
-
-#include <stdint.h>
-
-#define IBUF_READ_SIZE		65535
-#define IMSG_HEADER_SIZE	sizeof(struct imsg_hdr)
-#define MAX_IMSGSIZE		16384
-
-struct ibuf {
-	TAILQ_ENTRY(ibuf)	 entry;
-	unsigned char		*buf;
-	size_t			 size;
-	size_t			 max;
-	size_t			 wpos;
-	size_t			 rpos;
-	int			 fd;
-};
-
-struct msgbuf {
-	TAILQ_HEAD(, ibuf)	 bufs;
-	uint32_t		 queued;
-	int			 fd;
-};
-
-struct ibuf_read {
-	unsigned char		 buf[IBUF_READ_SIZE];
-	unsigned char		*rptr;
-	size_t			 wpos;
-};
-
-struct imsg_fd {
-	TAILQ_ENTRY(imsg_fd)	entry;
-	int			fd;
-};
-
-struct imsgbuf {
-	TAILQ_HEAD(, imsg_fd)	 fds;
-	struct ibuf_read	 r;
-	struct msgbuf		 w;
-	int			 fd;
-	pid_t			 pid;
-};
-
-#define IMSGF_HASFD	1
-
-struct imsg_hdr {
-	uint32_t	 type;
-	uint16_t	 len;
-	uint16_t	 flags;
-	uint32_t	 peerid;
-	uint32_t	 pid;
-};
-
-struct imsg {
-	struct imsg_hdr	 hdr;
-	int		 fd;
-	void		*data;
-};
-
-
-/* buffer.c */
-struct ibuf	*ibuf_open(size_t);
-struct ibuf	*ibuf_dynamic(size_t, size_t);
-int		 ibuf_add(struct ibuf *, const void *, size_t);
-void		*ibuf_reserve(struct ibuf *, size_t);
-void		*ibuf_seek(struct ibuf *, size_t, size_t);
-size_t		 ibuf_size(struct ibuf *);
-size_t		 ibuf_left(struct ibuf *);
-void		 ibuf_close(struct msgbuf *, struct ibuf *);
-int		 ibuf_write(struct msgbuf *);
-void		 ibuf_free(struct ibuf *);
-void		 msgbuf_init(struct msgbuf *);
-void		 msgbuf_clear(struct msgbuf *);
-int		 msgbuf_write(struct msgbuf *);
-void		 msgbuf_drain(struct msgbuf *, size_t);
-
-/* imsg.c */
-void	 imsg_init(struct imsgbuf *, int);
-ssize_t	 imsg_read(struct imsgbuf *);
-ssize_t	 imsg_get(struct imsgbuf *, struct imsg *);
-int	 imsg_compose(struct imsgbuf *, uint32_t, uint32_t, pid_t, int,
-	    const void *, uint16_t);
-int	 imsg_composev(struct imsgbuf *, uint32_t, uint32_t,  pid_t, int,
-	    const struct iovec *, int);
-struct ibuf *imsg_create(struct imsgbuf *, uint32_t, uint32_t, pid_t, uint16_t);
-int	 imsg_add(struct ibuf *, const void *, uint16_t);
-void	 imsg_close(struct imsgbuf *, struct ibuf *);
-void	 imsg_free(struct imsg *);
-int	 imsg_flush(struct imsgbuf *);
-void	 imsg_clear(struct imsgbuf *);
-
-#endif
blob - 9b11960def28ef34941658a0091836c135a3d8b4 (mode 644)
blob + /dev/null
--- compat/memmem.c
+++ /dev/null
@@ -1,183 +0,0 @@
-/*	$OpenBSD: memmem.c,v 1.5 2020/04/16 12:39:28 claudio Exp $ */
-
-/*
- * Copyright (c) 2005-2020 Rich Felker, et al.
- *
- * Permission is hereby granted, free of charge, to any person obtaining
- * a copy of this software and associated documentation files (the
- * "Software"), to deal in the Software without restriction, including
- * without limitation the rights to use, copy, modify, merge, publish,
- * distribute, sublicense, and/or sell copies of the Software, and to
- * permit persons to whom the Software is furnished to do so, subject to
- * the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
- * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- */
-
-#include <string.h>
-#include <stdint.h>
-
-static char *
-twobyte_memmem(const unsigned char *h, size_t k, const unsigned char *n)
-{
-	uint16_t nw = n[0]<<8 | n[1], hw = h[0]<<8 | h[1];
-	for (h+=2, k-=2; k; k--, hw = hw<<8 | *h++)
-		if (hw == nw) return (char *)h-2;
-	return hw == nw ? (char *)h-2 : 0;
-}
-
-static char *
-threebyte_memmem(const unsigned char *h, size_t k, const unsigned char *n)
-{
-	uint32_t nw = n[0]<<24 | n[1]<<16 | n[2]<<8;
-	uint32_t hw = h[0]<<24 | h[1]<<16 | h[2]<<8;
-	for (h+=3, k-=3; k; k--, hw = (hw|*h++)<<8)
-		if (hw == nw) return (char *)h-3;
-	return hw == nw ? (char *)h-3 : 0;
-}
-
-static char *
-fourbyte_memmem(const unsigned char *h, size_t k, const unsigned char *n)
-{
-	uint32_t nw = n[0]<<24 | n[1]<<16 | n[2]<<8 | n[3];
-	uint32_t hw = h[0]<<24 | h[1]<<16 | h[2]<<8 | h[3];
-	for (h+=4, k-=4; k; k--, hw = hw<<8 | *h++)
-		if (hw == nw) return (char *)h-4;
-	return hw == nw ? (char *)h-4 : 0;
-}
-
-#define MAX(a,b) ((a)>(b)?(a):(b))
-#define MIN(a,b) ((a)<(b)?(a):(b))
-
-#define BITOP(a,b,op) \
- ((a)[(size_t)(b)/(8*sizeof *(a))] op (size_t)1<<((size_t)(b)%(8*sizeof *(a))))
-
-/*
- * Maxime Crochemore and Dominique Perrin, Two-way string-matching,
- * Journal of the ACM, 38(3):651-675, July 1991.
- */
-static char *
-twoway_memmem(const unsigned char *h, const unsigned char *z,
-    const unsigned char *n, size_t l)
-{
-	size_t i, ip, jp, k, p, ms, p0, mem, mem0;
-	size_t byteset[32 / sizeof(size_t)] = { 0 };
-	size_t shift[256];
-
-	/* Computing length of needle and fill shift table */
-	for (i=0; i<l; i++)
-		BITOP(byteset, n[i], |=), shift[n[i]] = i+1;
-
-	/* Compute maximal suffix */
-	ip = -1; jp = 0; k = p = 1;
-	while (jp+k<l) {
-		if (n[ip+k] == n[jp+k]) {
-			if (k == p) {
-				jp += p;
-				k = 1;
-			} else k++;
-		} else if (n[ip+k] > n[jp+k]) {
-			jp += k;
-			k = 1;
-			p = jp - ip;
-		} else {
-			ip = jp++;
-			k = p = 1;
-		}
-	}
-	ms = ip;
-	p0 = p;
-
-	/* And with the opposite comparison */
-	ip = -1; jp = 0; k = p = 1;
-	while (jp+k<l) {
-		if (n[ip+k] == n[jp+k]) {
-			if (k == p) {
-				jp += p;
-				k = 1;
-			} else k++;
-		} else if (n[ip+k] < n[jp+k]) {
-			jp += k;
-			k = 1;
-			p = jp - ip;
-		} else {
-			ip = jp++;
-			k = p = 1;
-		}
-	}
-	if (ip+1 > ms+1) ms = ip;
-	else p = p0;
-
-	/* Periodic needle? */
-	if (memcmp(n, n+p, ms+1)) {
-		mem0 = 0;
-		p = MAX(ms, l-ms-1) + 1;
-	} else mem0 = l-p;
-	mem = 0;
-
-	/* Search loop */
-	for (;;) {
-		/* If remainder of haystack is shorter than needle, done */
-		if (z-h < l) return 0;
-
-		/* Check last byte first; advance by shift on mismatch */
-		if (BITOP(byteset, h[l-1], &)) {
-			k = l-shift[h[l-1]];
-			if (k) {
-				if (k < mem) k = mem;
-				h += k;
-				mem = 0;
-				continue;
-			}
-		} else {
-			h += l;
-			mem = 0;
-			continue;
-		}
-
-		/* Compare right half */
-		for (k=MAX(ms+1,mem); k<l && n[k] == h[k]; k++);
-		if (k < l) {
-			h += k-ms;
-			mem = 0;
-			continue;
-		}
-		/* Compare left half */
-		for (k=ms+1; k>mem && n[k-1] == h[k-1]; k--);
-		if (k <= mem) return (char *)h;
-		h += p;
-		mem = mem0;
-	}
-}
-
-void *
-memmem(const void *h0, size_t k, const void *n0, size_t l)
-{
-	const unsigned char *h = h0, *n = n0;
-
-	/* Return immediately on empty needle */
-	if (!l) return (void *)h;
-
-	/* Return immediately when needle is longer than haystack */
-	if (k<l) return 0;
-
-	/* Use faster algorithms for short needles */
-	h = memchr(h0, *n, k);
-	if (!h || l==1) return (void *)h;
-	k -= h - (const unsigned char *)h0;
-	if (k<l) return 0;
-	if (l==2) return twobyte_memmem(h, k, n);
-	if (l==3) return threebyte_memmem(h, k, n);
-	if (l==4) return fourbyte_memmem(h, k, n);
-
-	return twoway_memmem(h, h+k, n, l);
-}
blob - 578765401498b3c2b58b1994596d4204ba17d7a5 (mode 644)
blob + /dev/null
--- compat/ohash.c
+++ /dev/null
@@ -1,329 +0,0 @@
-/* $OpenBSD: ohash.c,v 1.1 2014/06/02 18:52:03 deraadt Exp $ */
-
-/* Copyright (c) 1999, 2004 Marc Espie <espie@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 "compat.h"
-
-#include <stddef.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-#include <limits.h>
-#include "ohash.h"
-
-struct _ohash_record {
-	uint32_t	hv;
-	const char	*p;
-};
-
-#define DELETED		((const char *)h)
-#define NONE		(h->size)
-
-/* Don't bother changing the hash table if the change is small enough.  */
-#define MINSIZE		(1UL << 4)
-#define MINDELETED	4
-
-static void ohash_resize(struct ohash *);
-
-
-/* This handles the common case of variable length keys, where the
- * key is stored at the end of the record.
- */
-void *
-ohash_create_entry(struct ohash_info *i, const char *start, const char **end)
-{
-	char *p;
-
-	if (!*end)
-		*end = start + strlen(start);
-	p = (i->alloc)(i->key_offset + (*end - start) + 1, i->data);
-	if (p) {
-		memcpy(p+i->key_offset, start, *end-start);
-		p[i->key_offset + (*end - start)] = '\0';
-	}
-	return (void *)p;
-}
-
-/* hash_delete only frees the hash structure. Use hash_first/hash_next
- * to free entries as well.  */
-void
-ohash_delete(struct ohash *h)
-{
-	(h->info.free)(h->t, h->info.data);
-#ifndef NDEBUG
-	h->t = NULL;
-#endif
-}
-
-static void
-ohash_resize(struct ohash *h)
-{
-	struct _ohash_record *n;
-	size_t ns;
-	unsigned int	j;
-	unsigned int	i, incr;
-
-	if (4 * h->deleted < h->total) {
-		if (h->size >= (UINT_MAX >> 1U))
-			ns = UINT_MAX;
-		else
-			ns = h->size << 1U;
-	} else if (3 * h->deleted > 2 * h->total)
-		ns = h->size >> 1U;
-	else
-		ns = h->size;
-	if (ns < MINSIZE)
-		ns = MINSIZE;
-#ifdef STATS_HASH
-	STAT_HASH_EXPAND++;
-	STAT_HASH_SIZE += ns - h->size;
-#endif
-
-	n = (h->info.calloc)(ns, sizeof(struct _ohash_record), h->info.data);
-	if (!n)
-		return;
-
-	for (j = 0; j < h->size; j++) {
-		if (h->t[j].p != NULL && h->t[j].p != DELETED) {
-			i = h->t[j].hv % ns;
-			incr = ((h->t[j].hv % (ns - 2)) & ~1) + 1;
-			while (n[i].p != NULL) {
-				i += incr;
-				if (i >= ns)
-					i -= ns;
-			}
-			n[i].hv = h->t[j].hv;
-			n[i].p = h->t[j].p;
-		}
-	}
-	(h->info.free)(h->t, h->info.data);
-	h->t = n;
-	h->size = ns;
-	h->total -= h->deleted;
-	h->deleted = 0;
-}
-
-void *
-ohash_remove(struct ohash *h, unsigned int i)
-{
-	void		*result = (void *)h->t[i].p;
-
-	if (result == NULL || result == DELETED)
-		return NULL;
-
-#ifdef STATS_HASH
-	STAT_HASH_ENTRIES--;
-#endif
-	h->t[i].p = DELETED;
-	h->deleted++;
-	if (h->deleted >= MINDELETED && 4 * h->deleted > h->total)
-		ohash_resize(h);
-	return result;
-}
-
-void *
-ohash_find(struct ohash *h, unsigned int i)
-{
-	if (h->t[i].p == DELETED)
-		return NULL;
-	else
-		return (void *)h->t[i].p;
-}
-
-void *
-ohash_insert(struct ohash *h, unsigned int i, void *p)
-{
-#ifdef STATS_HASH
-	STAT_HASH_ENTRIES++;
-#endif
-	if (h->t[i].p == DELETED) {
-		h->deleted--;
-		h->t[i].p = p;
-	} else {
-		h->t[i].p = p;
-		/* Arbitrary resize boundary.  Tweak if not efficient enough.  */
-		if (++h->total * 4 > h->size * 3)
-			ohash_resize(h);
-	}
-	return p;
-}
-
-unsigned int
-ohash_entries(struct ohash *h)
-{
-	return h->total - h->deleted;
-}
-
-void *
-ohash_first(struct ohash *h, unsigned int *pos)
-{
-	*pos = 0;
-	return ohash_next(h, pos);
-}
-
-void *
-ohash_next(struct ohash *h, unsigned int *pos)
-{
-	for (; *pos < h->size; (*pos)++)
-		if (h->t[*pos].p != DELETED && h->t[*pos].p != NULL)
-			return (void *)h->t[(*pos)++].p;
-	return NULL;
-}
-
-void
-ohash_init(struct ohash *h, unsigned int size, struct ohash_info *info)
-{
-	h->size = 1UL << size;
-	if (h->size < MINSIZE)
-		h->size = MINSIZE;
-#ifdef STATS_HASH
-	STAT_HASH_CREATION++;
-	STAT_HASH_SIZE += h->size;
-#endif
-	/* Copy info so that caller may free it.  */
-	h->info.key_offset = info->key_offset;
-	h->info.calloc = info->calloc;
-	h->info.free = info->free;
-	h->info.alloc = info->alloc;
-	h->info.data = info->data;
-	h->t = (h->info.calloc)(h->size, sizeof(struct _ohash_record),
-		    h->info.data);
-	h->total = h->deleted = 0;
-}
-
-uint32_t
-ohash_interval(const char *s, const char **e)
-{
-	uint32_t k;
-
-	if (!*e)
-		*e = s + strlen(s);
-	if (s == *e)
-		k = 0;
-	else
-		k = *s++;
-	while (s != *e)
-		k =  ((k << 2) | (k >> 30)) ^ *s++;
-	return k;
-}
-
-unsigned int
-ohash_lookup_interval(struct ohash *h, const char *start, const char *end,
-    uint32_t hv)
-{
-	unsigned int	i, incr;
-	unsigned int	empty;
-
-#ifdef STATS_HASH
-	STAT_HASH_LOOKUP++;
-#endif
-	empty = NONE;
-	i = hv % h->size;
-	incr = ((hv % (h->size-2)) & ~1) + 1;
-	while (h->t[i].p != NULL) {
-#ifdef STATS_HASH
-		STAT_HASH_LENGTH++;
-#endif
-		if (h->t[i].p == DELETED) {
-			if (empty == NONE)
-				empty = i;
-		} else if (h->t[i].hv == hv &&
-		    strncmp(h->t[i].p+h->info.key_offset, start,
-			end - start) == 0 &&
-		    (h->t[i].p+h->info.key_offset)[end-start] == '\0') {
-			if (empty != NONE) {
-				h->t[empty].hv = hv;
-				h->t[empty].p = h->t[i].p;
-				h->t[i].p = DELETED;
-				return empty;
-			} else {
-#ifdef STATS_HASH
-				STAT_HASH_POSITIVE++;
-#endif
-				return i;
-			}
-		}
-		i += incr;
-		if (i >= h->size)
-			i -= h->size;
-	}
-
-	/* Found an empty position.  */
-	if (empty != NONE)
-		i = empty;
-	h->t[i].hv = hv;
-	return i;
-}
-
-unsigned int
-ohash_lookup_memory(struct ohash *h, const char *k, size_t size, uint32_t hv)
-{
-	unsigned int	i, incr;
-	unsigned int	empty;
-
-#ifdef STATS_HASH
-	STAT_HASH_LOOKUP++;
-#endif
-	empty = NONE;
-	i = hv % h->size;
-	incr = ((hv % (h->size-2)) & ~1) + 1;
-	while (h->t[i].p != NULL) {
-#ifdef STATS_HASH
-		STAT_HASH_LENGTH++;
-#endif
-		if (h->t[i].p == DELETED) {
-			if (empty == NONE)
-				empty = i;
-		} else if (h->t[i].hv == hv &&
-		    memcmp(h->t[i].p+h->info.key_offset, k, size) == 0) {
-			if (empty != NONE) {
-				h->t[empty].hv = hv;
-				h->t[empty].p = h->t[i].p;
-				h->t[i].p = DELETED;
-				return empty;
-			} else {
-#ifdef STATS_HASH
-				STAT_HASH_POSITIVE++;
-#endif
-			}	return i;
-		}
-		i += incr;
-		if (i >= h->size)
-			i -= h->size;
-	}
-
-	/* Found an empty position.  */
-	if (empty != NONE)
-		i = empty;
-	h->t[i].hv = hv;
-	return i;
-}
-
-unsigned int
-ohash_qlookup(struct ohash *h, const char *s)
-{
-	const char *e = NULL;
-	return ohash_qlookupi(h, s, &e);
-}
-
-unsigned int
-ohash_qlookupi(struct ohash *h, const char *s, const char **e)
-{
-	uint32_t hv;
-
-	hv = ohash_interval(s, e);
-	return ohash_lookup_interval(h, s, *e, hv);
-}
blob - 3f070e2c7257456fa8401d225eea13c7eea93d1c (mode 644)
blob + /dev/null
--- compat/ohash.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/* $OpenBSD: ohash.h,v 1.2 2014/06/02 18:52:03 deraadt Exp $ */
-
-/* Copyright (c) 1999, 2004 Marc Espie <espie@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 OHASH_H
-#define OHASH_H
-
-/* Open hashing support. 
- * Open hashing was chosen because it is much lighter than other hash
- * techniques, and more efficient in most cases.
- */
-
-/* user-visible data structure */
-struct ohash_info {
-	ptrdiff_t key_offset;
-	void *data;	/* user data */
-	void *(*calloc)(size_t, size_t, void *);
-	void (*free)(void *, void *);
-	void *(*alloc)(size_t, void *);
-};
-
-struct _ohash_record;
-
-/* private structure. It's there just so you can do a sizeof */
-struct ohash {
-	struct _ohash_record 	*t;
-	struct ohash_info 	info;
-	unsigned int 		size;
-	unsigned int 		total;
-	unsigned int 		deleted;
-};
-
-/* For this to be tweakable, we use small primitives, and leave part of the
- * logic to the client application.  e.g., hashing is left to the client
- * application.  We also provide a simple table entry lookup that yields
- * a hashing table index (opaque) to be used in find/insert/remove.
- * The keys are stored at a known position in the client data.
- */
-void ohash_init(struct ohash *, unsigned, struct ohash_info *);
-void ohash_delete(struct ohash *);
-
-unsigned int ohash_lookup_interval(struct ohash *, const char *,
-	    const char *, uint32_t);
-unsigned int ohash_lookup_memory(struct ohash *, const char *,
-	    size_t, uint32_t);
-void *ohash_find(struct ohash *, unsigned int);
-void *ohash_remove(struct ohash *, unsigned int);
-void *ohash_insert(struct ohash *, unsigned int, void *);
-void *ohash_first(struct ohash *, unsigned int *);
-void *ohash_next(struct ohash *, unsigned int *);
-unsigned int ohash_entries(struct ohash *);
-
-void *ohash_create_entry(struct ohash_info *, const char *, const char **);
-uint32_t ohash_interval(const char *, const char **);
-
-unsigned int ohash_qlookupi(struct ohash *, const char *, const char **);
-unsigned int ohash_qlookup(struct ohash *, const char *);
-#endif
blob - bc1568be67482bf033c0da484f8bd9fd41426d03 (mode 644)
blob + /dev/null
--- compat/queue.h
+++ /dev/null
@@ -1,631 +0,0 @@
-/*	$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 - bbdbefedb14cf927c971d7f6f37b7192baeac977 (mode 644)
blob + /dev/null
--- compat/recallocarray.c
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * 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 "compat.h"
-
-#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 < 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 - 15c6f0991321ddd8d80d0f6ad75937e68c84a810 (mode 644)
blob + /dev/null
--- compat/setproctitle.c
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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 "compat.h"
-
-#include <sys/types.h>
-
-#include <stdarg.h>
-#include <stdio.h>
-#include <string.h>
-
-#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 - ddfd2925e5893cc1e9112bbe443911c66a82e3b1 (mode 644)
blob + /dev/null
--- compat/setprogname.c
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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 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 "../compat.h"
-
-void
-setprogname(const char *name)
-{
-	return;
-}
blob - c096bc91af0bd03b8a9700bdfe642541a00e2d1b (mode 644)
blob + /dev/null
--- compat/strlcat.c
+++ /dev/null
@@ -1,57 +0,0 @@
-/*	$OpenBSD: strlcat.c,v 1.19 2019/01/25 00:19:25 millert Exp $	*/
-
-/*
- * 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>
-
-#include "compat.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 - 5d17ee7e953b68de4fd0eb168e96e38b362631f8 (mode 644)
blob + /dev/null
--- compat/strlcpy.c
+++ /dev/null
@@ -1,52 +0,0 @@
-/*	$OpenBSD: strlcpy.c,v 1.16 2019/01/25 00:19:25 millert Exp $	*/
-
-/*
- * 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>
-
-#include "compat.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 - ea71d7bba91e7b03ad3b9049d129e90752f3c457 (mode 644)
blob + /dev/null
--- compat/strsep.c
+++ /dev/null
@@ -1,72 +0,0 @@
-/*	$OpenBSD: strsep.c,v 1.8 2015/08/31 02:53:57 guenther Exp $	*/
-
-/*-
- * Copyright (c) 1990, 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.
- */
-
-#include "compat.h"
-
-#include <string.h>
-
-/*
- * Get next token from string *stringp, where tokens are possibly-empty
- * strings separated by characters from delim.  
- *
- * Writes NULs into the string at *stringp to end tokens.
- * delim need not remain constant from call to call.
- * On return, *stringp points past the last NUL written (if there might
- * be further tokens), or is NULL (if there are definitely no more tokens).
- *
- * If *stringp is NULL, strsep returns NULL.
- */
-char *
-strsep(char **stringp, const char *delim)
-{
-	char *s;
-	const char *spanp;
-	int c, sc;
-	char *tok;
-
-	if ((s = *stringp) == NULL)
-		return (NULL);
-	for (tok = s;;) {
-		c = *s++;
-		spanp = delim;
-		do {
-			if ((sc = *spanp++) == c) {
-				if (c == 0)
-					s = NULL;
-				else
-					s[-1] = 0;
-				*stringp = s;
-				return (tok);
-			}
-		} while (sc != 0);
-	}
-	/* NOTREACHED */
-}
blob - a3d382ca22b6753ad4c1f7ab8bddaa9fa2eb4336 (mode 644)
blob + /dev/null
--- compat/strtonum.c
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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>
-
-#include "compat.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 - fb56e32567bed50a2455b9b8fb87e809bba9acd0 (mode 644)
blob + /dev/null
--- compat/tree.h
+++ /dev/null
@@ -1,1012 +0,0 @@
-/*	$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 - 1b8ec6ca2b31e2f06cfed55fcc17f69e53f30573 (mode 644)
blob + /dev/null
--- compat/vis.c
+++ /dev/null
@@ -1,242 +0,0 @@
-/*	$OpenBSD: vis.c,v 1.25 2015/09/13 11:32:51 guenther Exp $ */
-/*-
- * Copyright (c) 1989, 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.
- */
-
-#include "compat.h"
-
-#include <sys/types.h>
-#include <errno.h>
-#include <ctype.h>
-#include <limits.h>
-#include <string.h>
-#include <stdlib.h>
-
-#define	isoctal(c)	(((u_char)(c)) >= '0' && ((u_char)(c)) <= '7')
-#define	isvisible(c,flag)						\
-	(((c) == '\\' || (flag & VIS_ALL) == 0) &&			\
-	(((u_int)(c) <= UCHAR_MAX && isascii((u_char)(c)) &&		\
-	(((c) != '*' && (c) != '?' && (c) != '[' && (c) != '#') ||	\
-		(flag & VIS_GLOB) == 0) && isgraph((u_char)(c))) ||	\
-	((flag & VIS_SP) == 0 && (c) == ' ') ||				\
-	((flag & VIS_TAB) == 0 && (c) == '\t') ||			\
-	((flag & VIS_NL) == 0 && (c) == '\n') ||			\
-	((flag & VIS_SAFE) && ((c) == '\b' ||				\
-		(c) == '\007' || (c) == '\r' ||				\
-		isgraph((u_char)(c))))))
-
-/*
- * vis - visually encode characters
- */
-char *
-vis(char *dst, int c, int flag, int nextc)
-{
-	if (isvisible(c, flag)) {
-		if ((c == '"' && (flag & VIS_DQ) != 0) ||
-		    (c == '\\' && (flag & VIS_NOSLASH) == 0))
-			*dst++ = '\\';
-		*dst++ = c;
-		*dst = '\0';
-		return (dst);
-	}
-
-	if (flag & VIS_CSTYLE) {
-		switch(c) {
-		case '\n':
-			*dst++ = '\\';
-			*dst++ = 'n';
-			goto done;
-		case '\r':
-			*dst++ = '\\';
-			*dst++ = 'r';
-			goto done;
-		case '\b':
-			*dst++ = '\\';
-			*dst++ = 'b';
-			goto done;
-		case '\a':
-			*dst++ = '\\';
-			*dst++ = 'a';
-			goto done;
-		case '\v':
-			*dst++ = '\\';
-			*dst++ = 'v';
-			goto done;
-		case '\t':
-			*dst++ = '\\';
-			*dst++ = 't';
-			goto done;
-		case '\f':
-			*dst++ = '\\';
-			*dst++ = 'f';
-			goto done;
-		case ' ':
-			*dst++ = '\\';
-			*dst++ = 's';
-			goto done;
-		case '\0':
-			*dst++ = '\\';
-			*dst++ = '0';
-			if (isoctal(nextc)) {
-				*dst++ = '0';
-				*dst++ = '0';
-			}
-			goto done;
-		}
-	}
-	if (((c & 0177) == ' ') || (flag & VIS_OCTAL) ||
-	    ((flag & VIS_GLOB) && (c == '*' || c == '?' || c == '[' || c == '#'))) {
-		*dst++ = '\\';
-		*dst++ = ((u_char)c >> 6 & 07) + '0';
-		*dst++ = ((u_char)c >> 3 & 07) + '0';
-		*dst++ = ((u_char)c & 07) + '0';
-		goto done;
-	}
-	if ((flag & VIS_NOSLASH) == 0)
-		*dst++ = '\\';
-	if (c & 0200) {
-		c &= 0177;
-		*dst++ = 'M';
-	}
-	if (iscntrl((u_char)c)) {
-		*dst++ = '^';
-		if (c == 0177)
-			*dst++ = '?';
-		else
-			*dst++ = c + '@';
-	} else {
-		*dst++ = '-';
-		*dst++ = c;
-	}
-done:
-	*dst = '\0';
-	return (dst);
-}
-
-/*
- * strvis, strnvis, strvisx - visually encode characters from src into dst
- *	
- *	Dst must be 4 times the size of src to account for possible
- *	expansion.  The length of dst, not including the trailing NULL,
- *	is returned. 
- *
- *	Strnvis will write no more than siz-1 bytes (and will NULL terminate).
- *	The number of bytes needed to fully encode the string is returned.
- *
- *	Strvisx encodes exactly len bytes from src into dst.
- *	This is useful for encoding a block of data.
- */
-int
-strvis(char *dst, const char *src, int flag)
-{
-	char c;
-	char *start;
-
-	for (start = dst; (c = *src);)
-		dst = vis(dst, c, flag, *++src);
-	*dst = '\0';
-	return (dst - start);
-}
-
-int
-strnvis(char *dst, const char *src, size_t siz, int flag)
-{
-	char *start, *end;
-	char tbuf[5];
-	int c, i;
-
-	i = 0;
-	for (start = dst, end = start + siz - 1; (c = *src) && dst < end; ) {
-		if (isvisible(c, flag)) {
-			if ((c == '"' && (flag & VIS_DQ) != 0) ||
-			    (c == '\\' && (flag & VIS_NOSLASH) == 0)) {
-				/* need space for the extra '\\' */
-				if (dst + 1 >= end) {
-					i = 2;
-					break;
-				}
-				*dst++ = '\\';
-			}
-			i = 1;
-			*dst++ = c;
-			src++;
-		} else {
-			i = vis(tbuf, c, flag, *++src) - tbuf;
-			if (dst + i <= end) {
-				memcpy(dst, tbuf, i);
-				dst += i;
-			} else {
-				src--;
-				break;
-			}
-		}
-	}
-	if (siz > 0)
-		*dst = '\0';
-	if (dst + i > end) {
-		/* adjust return value for truncation */
-		while ((c = *src))
-			dst += vis(tbuf, c, flag, *++src) - tbuf;
-	}
-	return (dst - start);
-}
-
-int
-stravis(char **outp, const char *src, int flag)
-{
-	char *buf;
-	int len, serrno;
-
-	buf = reallocarray(NULL, 4, strlen(src) + 1);
-	if (buf == NULL)
-		return -1;
-	len = strvis(buf, src, flag);
-	serrno = errno;
-	*outp = realloc(buf, len + 1);
-	if (*outp == NULL) {
-		*outp = buf;
-		errno = serrno;
-	}
-	return (len);
-}
-
-int
-strvisx(char *dst, const char *src, size_t len, int flag)
-{
-	char c;
-	char *start;
-
-	for (start = dst; len > 1; len--) {
-		c = *src;
-		dst = vis(dst, c, flag, *++src);
-	}
-	if (len)
-		dst = vis(dst, *src, flag, '\0');
-	*dst = '\0';
-	return (dst - start);
-}
blob - 59b7d6b81777229668dfbb0bb1aaaca8e126eb8c (mode 644)
blob + /dev/null
--- compat/vis.h
+++ /dev/null
@@ -1,90 +0,0 @@
-/*	$OpenBSD: vis.h,v 1.15 2015/07/20 01:52:27 millert Exp $	*/
-/*	$NetBSD: vis.h,v 1.4 1994/10/26 00:56:41 cgd Exp $	*/
-
-/*-
- * Copyright (c) 1990 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.
- *
- *	@(#)vis.h	5.9 (Berkeley) 4/3/91
- */
-
-#ifndef _VIS_H_
-#define	_VIS_H_
-
-/*
- * to select alternate encoding format
- */
-#define	VIS_OCTAL	0x01	/* use octal \ddd format */
-#define	VIS_CSTYLE	0x02	/* use \[nrft0..] where appropriate */
-
-/*
- * to alter set of characters encoded (default is to encode all
- * non-graphic except space, tab, and newline).
- */
-#define	VIS_SP		0x04	/* also encode space */
-#define	VIS_TAB		0x08	/* also encode tab */
-#define	VIS_NL		0x10	/* also encode newline */
-#define	VIS_WHITE	(VIS_SP | VIS_TAB | VIS_NL)
-#define	VIS_SAFE	0x20	/* only encode "unsafe" characters */
-#define	VIS_DQ		0x200	/* backslash-escape double quotes */
-#define	VIS_ALL		0x400	/* encode all characters */
-
-/*
- * other
- */
-#define	VIS_NOSLASH	0x40	/* inhibit printing '\' */
-#define	VIS_GLOB	0x100	/* encode glob(3) magics and '#' */
-
-/*
- * unvis return codes
- */
-#define	UNVIS_VALID	 1	/* character valid */
-#define	UNVIS_VALIDPUSH	 2	/* character valid, push back passed char */
-#define	UNVIS_NOCHAR	 3	/* valid sequence, no character produced */
-#define	UNVIS_SYNBAD	-1	/* unrecognized escape sequence */
-#define	UNVIS_ERROR	-2	/* decoder in unknown state (unrecoverable) */
-
-/*
- * unvis flags
- */
-#define	UNVIS_END	1	/* no more characters */
-
-#include <sys/cdefs.h>
-
-char	*vis(char *, int, int, int);
-int	strvis(char *, const char *, int);
-int	stravis(char **, const char *, int);
-int	strnvis(char *, const char *, size_t, int)
-		__attribute__ ((__bounded__(__string__,1,3)));
-int	strvisx(char *, const char *, size_t, int)
-		__attribute__ ((__bounded__(__string__,1,3)));
-int	strunvis(char *, const char *);
-int	unvis(char *, char, int *, int);
-ssize_t strnunvis(char *, const char *, size_t)
-		__attribute__ ((__bounded__(__string__,1,3)));
-
-#endif /* !_VIS_H_ */
blob - /dev/null
blob + 7de065f7200b6f32f7f8e5fd9ada3f4e07f51ad2 (mode 644)
--- /dev/null
+++ kamirepl/Makefile
@@ -0,0 +1,17 @@
+.PATH:${.CURDIR}/../lib
+
+.include "../kamid-version.mk"
+
+PROG=		kamirepl
+SRCS=		9pclib.c kamirepl.c log.c utils.c
+
+CPPFLAGS=	-I${.CURDIR}/../lib -I${.CURDIR}
+
+LDADD+= -levent -lutil -ltls
+DPADD+= ${LIBEVENT} ${LIBUTIL} ${LIBTLS}
+
+.if ${KAMID_RELEASE} != Yes
+NOMAN= Yes
+.endif
+
+.include <bsd.prog.mk>
blob - /dev/null
blob + fb0c86530732d462858c57359e8a544d794d6b06 (mode 644)
--- /dev/null
+++ kamirepl/kamirepl.1
@@ -0,0 +1,152 @@
+.\" 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.
+.\"
+.Dd $Mdocdate: December 16 2021 $
+.Dt KAMIREPL 1
+.Os
+.Sh NAME
+.Nm kamirepl
+.Nd 9p repl client
+.Sh SYNOPSIS
+.Nm
+.Op Fl chv
+.Op Fl C Ar cert
+.Op Fl H Ar host
+.Op Fl K Ar key
+.Op Fl P Ar port
+.Sh DESCRIPTION
+.Nm
+is a 
+.Xr 9p 7
+repl client.
+.Pp
+The optinos are as follows:
+.Bl -tag -width tenletters
+.It Fl C Ar cert
+Path to the TLS client certificate to use.
+.It Fl c
+Use TLS for the connection.
+.Fl C
+and
+.Fl K
+are mandatory if used.
+.It Fl H Ar host
+Hostname of the file server.
+.It Fl h
+Display usage and exit.
+.It Fl K Ar key
+Path to the TLS client certificate private key.
+.It Fl P Ar port
+Port number to connect to.
+.It Fl v
+Verbose logging.
+.El
+.Pp
+The interactive commands are
+.Bl -tag -width Ds
+.It Ic version Op Ar version-string
+.Ar version-string
+is
+.Dq 9P2000
+by default.
+.It Ic attach Ar fid Ar uname Ar aname
+Request the file server to attach the file tree identified by
+.Ar aname
+to the specified
+.Ar fid
+number.
+.Ar aname
+is the identifier for the user.
+The afid used is implicitly NOFID.
+.It Ic clunk Ar fid
+Closes
+.Ar fid.
+.It Ic flush Ar oldtag
+Require the server to flush
+.Ar oldtag .
+.It Ic walk Ar fid Ar newfid Ar wnames...
+Do a walk from
+.Ar fid
+following
+.Ar wnames
+component and associating the reached file to
+.Ar newfid .
+.It Ic open Ar fid Ar mode Op Ar flag
+Prepare
+.Ar fid
+for I/O.
+.Ar mode
+can be one of
+.Sq read
+or
+.Sq r ,
+.Sq write
+or
+.Sq w ,
+.Sq readwrite
+or
+.Sq rdwr .
+Optionally,
+.Ar flag
+can be on of
+.Sq trunc
+to truncate the file or
+.Sq rclose
+to remove the file upon
+.Ic clunk .
+.It Ic create Ar fid Ar name Ar perm Ar mode
+Create the file
+.Ar name
+and open it with
+.Ar mode
+as the given
+.Ar fid.
+.Ar perm
+should be used to select the permissions of the file, but is currently
+unused.
+.It Ic read Ar fid Ar offset Ar count
+Issue a read request for the given
+.Ar fid ,
+which must have been prepared for I/O with
+.Ic open ,
+at
+.Ar offset
+and for
+.Ar count
+bytes.
+.It Ic write Ar fid Ar offset Ar content
+Writes
+.Ar content
+to
+.Ar fid
+starting at
+.Ar offset .
+.It Ic remove Ar fid
+Delete the file identified by
+.Ar fid
+and close it.
+Even in case of error,
+.Ar fid
+is clunked.
+.El
+.Sh SEE ALSO
+.Xr kamiftp 1
+.Xr 9p 7
+.Xr kamid 8
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+utility was written by
+.An Omar Polo Aq Mt op@omarpolo.com .
blob - /dev/null
blob + d811e395f1890a7cf094ee8612574ff27b3e5763 (mode 644)
--- /dev/null
+++ kamirepl/kamirepl.c
@@ -0,0 +1,1074 @@
+/*
+ * 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 <assert.h>
+#include <endian.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <tls.h>
+#include <unistd.h>
+#include <vis.h>
+
+#include "9pclib.h"
+#include "kami.h"
+#include "log.h"
+#include "utils.h"
+
+#define DEBUG_PACKETS 0
+
+#define PROMPT "=% "
+
+/* flags */
+int			 verbose;
+int			 tls;
+const char		*keypath;
+const char		*crtpath;
+const char		*host;
+const char		*port;
+
+/* state */
+struct tls_config	*tlsconf;
+struct tls		*ctx;
+struct bufferevent	*bev, *inbev;
+
+static void __dead	 usage(int);
+
+static void		 sig_handler(int, short, void *);
+
+static int		 openconn(void);
+static void		 mark_nonblock(int);
+
+static void		 tls_readcb(int, short, void *);
+static void		 tls_writecb(int, short, void *);
+
+static void		 client_read(struct bufferevent *, void *);
+static void		 client_write(struct bufferevent *, void *);
+static void		 client_error(struct bufferevent *, short, void *);
+
+static void		 repl_read(struct bufferevent *, void *);
+static void		 repl_error(struct bufferevent *, short, void *);
+
+static void		 excmd_version(const char **, int);
+static void		 excmd_attach(const char **, int);
+static void		 excmd_clunk(const char **, int);
+static void		 excmd_flush(const char **, int);
+static void		 excmd_walk(const char ** , int);
+static void		 excmd_open(const char ** , int);
+static void		 excmd_create(const char ** , int);
+static void		 excmd_read(const char ** , int);
+static void		 excmd_write(const char **, int);
+static void		 excmd(const char **, int);
+
+static void		 pp_qid(const uint8_t *, uint32_t);
+static void		 pp_msg(uint32_t, uint8_t, uint16_t, const uint8_t *);
+static void		 handle_9p(const uint8_t *, size_t);
+static void		 clr(void);
+static void		 prompt(void);
+
+static void __dead
+usage(int ret)
+{
+	fprintf(stderr,
+	    "usage: %s [-chv] [-C crt] [-K key] [-H host] [-P port]\n",
+	    getprogname());
+	fprintf(stderr, "kamid suite version " KAMID_VERSION "\n");
+	exit(ret);
+}
+
+static void
+sig_handler(int sig, short event, void *d)
+{
+	/*
+	 * Normal signal handler rules don't apply because libevent
+	 * decouples for us.
+	 */
+
+	switch (sig) {
+	case SIGINT:
+	case SIGTERM:
+		clr();
+		log_warnx("Shutting down...");
+		event_loopbreak();
+		return;
+	default:
+		fatalx("unexpected signal %d", sig);
+	}
+}
+
+static int
+openconn(void)
+{
+	struct addrinfo hints, *res, *res0;
+	int error;
+	int save_errno;
+	int s;
+	const char *cause = NULL;
+
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_family = AF_UNSPEC;
+	hints.ai_socktype = SOCK_STREAM;
+	if ((error = getaddrinfo(host, port, &hints, &res0))) {
+		warnx("%s", gai_strerror(error));
+		return -1;
+	}
+
+	s = -1;
+	for (res = res0; res; res = res->ai_next) {
+		s = socket(res->ai_family, res->ai_socktype,
+		    res->ai_protocol);
+		if (s == -1) {
+			cause = "socket";
+			continue;
+		}
+
+		if (connect(s, res->ai_addr, res->ai_addrlen) == -1) {
+			cause = "connect";
+			save_errno = errno;
+			close(s);
+			errno = save_errno;
+			s = -1;
+			continue;
+		}
+
+		break;
+	}
+
+	freeaddrinfo(res0);
+
+	if (s == -1)
+		warn("%s", cause);
+
+	return s;
+}
+
+static void
+mark_nonblock(int fd)
+{
+	int flags;
+
+	if ((flags = fcntl(fd, F_GETFL)) == -1)
+		fatal("fcntl(F_GETFL)");
+	if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
+		fatal("fcntl(F_SETFL)");
+}
+
+static void
+tls_readcb(int fd, short event, void *d)
+{
+	struct bufferevent	*bufev = d;
+	char			 buf[IBUF_READ_SIZE];
+	int			 what = EVBUFFER_READ;
+	int			 howmuch = IBUF_READ_SIZE;
+	ssize_t			 ret;
+	size_t			 len;
+
+	if (event == EV_TIMEOUT) {
+		what |= EVBUFFER_TIMEOUT;
+		goto err;
+	}
+
+	if (bufev->wm_read.high != 0)
+		howmuch = MIN(sizeof(buf), bufev->wm_read.high);
+
+	switch (ret = tls_read(ctx, buf, howmuch)) {
+	case TLS_WANT_POLLIN:
+	case TLS_WANT_POLLOUT:
+		goto retry;
+	case -1:
+		what |= EVBUFFER_ERROR;
+		goto err;
+	}
+	len = ret;
+
+	if (len == 0) {
+		what |= EVBUFFER_EOF;
+		goto err;
+	}
+
+	if (evbuffer_add(bufev->input, buf, len) == -1) {
+		what |= EVBUFFER_ERROR;
+		goto err;
+	}
+
+	event_add(&bufev->ev_read, NULL);
+
+	len = EVBUFFER_LENGTH(bufev->input);
+	if (bufev->wm_read.low != 0 && len < bufev->wm_read.low)
+		return;
+	if (bufev->readcb != NULL)
+		(*bufev->readcb)(bufev, bufev->cbarg);
+	return;
+
+retry:
+	event_add(&bufev->ev_read, NULL);
+	return;
+
+err:
+	(*bufev->errorcb)(bufev, what, bufev->cbarg);
+}
+
+static void
+tls_writecb(int fd, short event, void *d)
+{
+	struct bufferevent	*bufev = d;
+	ssize_t			 ret;
+	size_t			 len;
+	short			 what = EVBUFFER_WRITE;
+	void			*data;
+
+	if (event == EV_TIMEOUT) {
+		what |= EVBUFFER_TIMEOUT;
+		goto err;
+	}
+
+	len = EVBUFFER_LENGTH(bufev->output);
+	if (len != 0) {
+		data = EVBUFFER_DATA(bufev->output);
+
+#if DEBUG_PACKETS
+		hexdump("outgoing msg", data, len);
+#endif
+
+		switch (ret = tls_write(ctx, data, len)) {
+		case TLS_WANT_POLLIN:
+		case TLS_WANT_POLLOUT:
+			goto retry;
+		case -1:
+			what |= EVBUFFER_ERROR;
+			goto err;
+		}
+		evbuffer_drain(bufev->output, ret);
+	}
+
+	if (EVBUFFER_LENGTH(bufev->output) != 0)
+		event_add(&bufev->ev_write, NULL);
+
+	if (bufev->writecb != NULL &&
+	    EVBUFFER_LENGTH(bufev->output) <= bufev->wm_write.low)
+		(*bufev->writecb)(bufev, bufev->cbarg);
+	return;
+
+retry:
+	event_add(&bufev->ev_write, NULL);
+	return;
+err:
+	(*bufev->errorcb)(bufev, what, bufev->cbarg);
+}
+
+static void
+client_read(struct bufferevent *bev, void *d)
+{
+	struct evbuffer	*src = EVBUFFER_INPUT(bev);
+	uint32_t	 len;
+	uint8_t		*data;
+
+	for (;;) {
+		if (EVBUFFER_LENGTH(src) < sizeof(len))
+			return;
+
+		data = EVBUFFER_DATA(src);
+
+		memcpy(&len, data, sizeof(len));
+		len = le32toh(len);
+
+		if (len < HEADERSIZE)
+			fatal("incoming message is too small! (%d bytes)",
+			    len);
+
+		if (len > EVBUFFER_LENGTH(src))
+			return;
+
+#if DEBUG_PACKETS
+		hexdump("incoming msg", data, len);
+#endif
+
+		handle_9p(data, len);
+		evbuffer_drain(src, len);
+	}
+}
+
+static void
+client_write(struct bufferevent *bev, void *data)
+{
+	return; /* nothing to do */
+}
+
+static void
+client_error(struct bufferevent *bev, short err, void *data)
+{
+	if (err & EVBUFFER_ERROR)
+		fatal("buffer event error");
+
+	if (err & EVBUFFER_EOF) {
+		clr();
+		log_info("EOF");
+		event_loopbreak();
+		return;
+	}
+
+	clr();
+	log_warnx("unknown event error");
+	event_loopbreak();
+}
+
+static void
+repl_read(struct bufferevent *bev, void *d)
+{
+	size_t		 len;
+	int		 argc;
+	const char	*argv[10], **ap;
+	char		*line;
+
+	line = evbuffer_readln(bev->input, &len, EVBUFFER_EOL_LF);
+	if (line == NULL)
+		return;
+
+	for (argc = 0, ap = argv; ap < &argv[9] &&
+	    (*ap = strsep(&line, " \t")) != NULL;) {
+		if (**ap != '\0')
+			ap++, argc++;
+	}
+
+	clr();
+	excmd(argv, argc);
+	prompt();
+
+	free(line);
+}
+
+static void
+repl_error(struct bufferevent *bev, short error, void *d)
+{
+	fatalx("an error occurred");
+}
+
+static inline void
+do_send(void)
+{
+	bufferevent_write_buffer(bev, evb);
+}
+
+/* version [version-str] */
+static void
+excmd_version(const char **argv, int argc)
+{
+	const char	*s;
+
+	s = VERSION9P;
+	if (argc == 2)
+		s = argv[1];
+
+	tversion(s, MSIZE9P);
+	do_send();
+}
+
+/* attach fid uname aname */
+static void
+excmd_attach(const char **argv, int argc)
+{
+	uint32_t	 fid;
+	const char	*errstr;
+
+	if (argc != 4)
+		goto usage;
+
+        fid = strtonum(argv[1], 0, UINT32_MAX, &errstr);
+	if (errstr != NULL) {
+		log_warnx("fid is %s: %s", errstr, argv[1]);
+		return;
+	}
+
+	tattach(fid, NOFID, argv[2], argv[3]);
+	do_send();
+	return;
+
+usage:
+	log_warnx("usage: attach fid uname aname");
+}
+
+/* clunk fid */
+static void
+excmd_clunk(const char **argv, int argc)
+{
+	uint32_t	 fid;
+	const char	*errstr;
+
+	if (argc != 2)
+		goto usage;
+
+	fid = strtonum(argv[1], 0, UINT32_MAX, &errstr);
+	if (errstr != NULL) {
+		log_warnx("fid is %s: %s", errstr, argv[1]);
+		return;
+	}
+
+	tclunk(fid);
+	do_send();
+	return;
+
+usage:
+	log_warnx("usage: clunk fid");
+}
+
+/* flush oldtag */
+static void
+excmd_flush(const char **argv, int argc)
+{
+	uint16_t	 oldtag;
+	const char	*errstr;
+
+	if (argc != 2)
+		goto usage;
+
+	oldtag = strtonum(argv[1], 0, UINT16_MAX, &errstr);
+	if (errstr != NULL) {
+		log_warnx("oldtag is %s: %s", errstr, argv[1]);
+		return;
+	}
+
+	tflush(oldtag);
+	do_send();
+	return;
+
+usage:
+	log_warnx("usage: flush oldtag");
+}
+
+/* walk fid newfid wnames... */
+static void
+excmd_walk(const char **argv, int argc)
+{
+	uint32_t	 fid, newfid;
+	const char	*errstr;
+
+	if (argc < 3)
+		goto usage;
+
+	fid = strtonum(argv[1], 0, UINT32_MAX, &errstr);
+	if (errstr != NULL) {
+		log_warnx("fid is %s: %s", errstr, argv[1]);
+		return;
+	}
+
+	newfid = strtonum(argv[2], 0, UINT32_MAX, &errstr);
+	if (errstr != NULL) {
+		log_warnx("newfid is %s: %s", errstr, argv[1]);
+		return;
+	}
+
+	twalk(fid, newfid, argv + 3, argc - 3);
+	do_send();
+	return;
+
+usage:
+	log_warnx("usage: walk fid newfid wnames...");
+}
+
+/* open fid mode [flag] */
+static void
+excmd_open(const char **argv, int argc)
+{
+	const char	*errstr;
+	uint32_t	 fid;
+	uint8_t		 mode = 0;
+
+	if (argc != 3 && argc != 4)
+		goto usage;
+
+	fid = strtonum(argv[1], 0, UINT32_MAX, &errstr);
+	if (errstr != NULL) {
+		log_warnx("fid is %s: %s", errstr, argv[1]);
+		return;
+	}
+
+	/* parse mode */
+	if (!strcmp("read", argv[2]) || !strcmp("r", argv[2]))
+		mode = KOREAD;
+	else if (!strcmp("write", argv[2]) || !strcmp("w", argv[2]))
+		mode = KOWRITE;
+	else if (!strcmp("readwrite", argv[2]) || !strcmp("rw", argv[2]))
+		mode = KORDWR;
+	else {
+		log_warnx("invalid mode %s", argv[2]);
+		return;
+	}
+
+	/* parse flag */
+	if (argv[3] != NULL) {
+		if (!strcmp("trunc", argv[3]))
+			mode |= KOTRUNC;
+		else if (!strcmp("rclose", argv[3]))
+			mode |= KORCLOSE;
+		else {
+			log_warnx("invalid flag %s", argv[3]);
+			return;
+		}
+	}
+
+	topen(fid, mode);
+	do_send();
+	return;
+
+usage:
+	log_warnx("usage: open fid mode [flag]");
+}
+
+/* create fid path perm mode */
+static void
+excmd_create(const char **argv, int argc)
+{
+	const char	*errstr;
+	uint32_t	 fid;
+	uint8_t		 mode = 0;
+
+	if (argc != 5)
+		goto usage;
+
+	fid = strtonum(argv[1], 0, UINT32_MAX, &errstr);
+	if (errstr != NULL) {
+		log_warnx("fid is %s: %s", errstr, argv[1]);
+		return;
+	}
+
+	/* parse mode */
+	if (!strcmp("write", argv[4]) || !strcmp("w", argv[4]))
+		mode = KOWRITE;
+	else if (!strcmp("readwrite", argv[4]) || !strcmp("rw", argv[4]))
+		mode = KORDWR;
+	else {	    
+		log_warnx("invalid mode %s for create", argv[4]);
+		return;
+	}
+
+	tcreate(fid, argv[2], 0, mode);
+	do_send();
+	return;
+
+usage:
+	log_warnx("usage: create fid path perm mode ; perm is unused");
+}
+
+
+/* read fid offset count */
+static void
+excmd_read(const char **argv, int argc)
+{
+	uint64_t	 off;
+	uint32_t	 fid, count;
+	const char	*errstr;
+
+	if (argc != 4)
+		goto usage;
+
+	fid = strtonum(argv[1], 0, UINT32_MAX, &errstr);
+	if (errstr != NULL) {
+		log_warnx("fid is %s: %s", errstr, argv[1]);
+		return;
+	}
+
+	/* should really be UNT64_MAX but it fails... */
+	off = strtonum(argv[2], -1, UINT32_MAX, &errstr);
+	if (errstr != NULL) {
+		log_warnx("offset is %s: %s", errstr, argv[2]);
+		return;
+	}
+
+	count = strtonum(argv[3], 0, UINT32_MAX, &errstr);
+	if (errstr != NULL) {
+		log_warnx("count is %s: %s", errstr, argv[3]);
+		return;
+	}
+
+	tread(fid, off, count);
+	do_send();
+	return;
+
+usage:
+	log_warnx("usage: read fid offset count");
+}
+
+/* write fid offset content */
+static void
+excmd_write(const char **argv, int argc)
+{
+	uint64_t	 off;
+	uint32_t	 fid, count;
+	const char	*errstr;
+
+	if (argc != 4)
+		goto usage;
+
+	fid = strtonum(argv[1], 0, UINT32_MAX, &errstr);
+	if (errstr != NULL) {
+		log_warnx("fid is %s: %s", errstr, argv[1]);
+		return;
+	}
+
+	/* should really be UINT64_MAX but... */
+	off = strtonum(argv[2], 0, UINT32_MAX, &errstr);
+	if (errstr != NULL) {
+		log_warnx("offset is %s: %s", errstr, argv[2]);
+		return;
+	}
+
+	count = strlen(argv[3]);
+	twrite(fid, off, argv[3], count);
+	do_send();
+	return;
+
+usage:
+	log_warnx("usage: write fid offset content");
+}
+
+/* remove fid */
+static void
+excmd_remove(const char **argv, int argc)
+{
+	const char	*errstr;
+	uint32_t	 fid;
+
+	if (argc != 2)
+		goto usage;
+
+	fid = strtonum(argv[1], 0, UINT32_MAX, &errstr);
+	if (errstr != NULL) {
+		log_warnx("fid is %s: %s", errstr, argv[1]);
+		return;
+	}
+
+	tremove(fid);
+	do_send();
+	return;
+
+usage:
+	log_warnx("usage: remove fid");
+}
+
+static void
+excmd(const char **argv, int argc)
+{
+	struct cmd {
+		const char	*name;
+		void		(*fn)(const char **, int);
+	} cmds[] = {
+		{"version",	excmd_version},
+		{"attach",	excmd_attach},
+		{"clunk",	excmd_clunk},
+		{"flush",	excmd_flush},
+		{"walk",	excmd_walk},
+		{"open",	excmd_open},
+		{"create",	excmd_create},
+		{"read",	excmd_read},
+		{"write",	excmd_write},
+		/* TODO: stat */
+		{"remove",	excmd_remove},
+	};
+	size_t i;
+
+	if (argc == 0)
+		return;
+
+	for (i = 0; i < sizeof(cmds)/sizeof(cmds[0]); ++i) {
+		if (!strcmp(cmds[i].name, argv[0])) {
+			cmds[i].fn(argv, argc);
+			return;
+		}
+	}
+
+	log_warnx("Unknown command %s", *argv);
+}
+
+static void
+pp_qid(const uint8_t *d, uint32_t len)
+{
+	uint64_t	path;
+	uint32_t	vers;
+	uint8_t		type;
+
+	if (len < 13) {
+		printf("invalid");
+		return;
+	}
+
+	type = *d++;
+
+	memcpy(&vers, d, sizeof(vers));
+	d += sizeof(vers);
+	vers = le64toh(vers);
+
+	memcpy(&path, d, sizeof(path));
+	d += sizeof(path);
+	path = le64toh(path);
+
+	printf("qid{path=%"PRIu64" version=%"PRIu32" type=0x%x\"%s\"}",
+	    path, vers, type, pp_qid_type(type));
+}
+
+static void
+pp_msg(uint32_t len, uint8_t type, uint16_t tag, const uint8_t *d)
+{
+	uint32_t	 msize, iounit, count;
+	uint16_t	 slen;
+	char		*v;
+
+	printf("len=%"PRIu32" type=%d[%s] tag=0x%x[%d] ", len,
+	    type, pp_msg_type(type), tag, tag);
+
+	len -= HEADERSIZE;
+
+	switch (type) {
+	case Rversion:
+		if (len < 6) {
+			printf("invalid: not enough space for msize "
+			    "and version provided.");
+			break;
+		}
+
+		memcpy(&msize, d, sizeof(msize));
+		d += sizeof(msize);
+		len -= sizeof(msize);
+		msize = le32toh(msize);
+
+		memcpy(&slen, d, sizeof(slen));
+		d += sizeof(slen);
+		len -= sizeof(slen);
+		slen = le16toh(slen);
+
+		if (len != slen) {
+			printf("invalid: version string length doesn't "
+			    "match.  Got %d; want %d", slen, len);
+			break;
+		}
+
+		printf("msize=%"PRIu32" version[%"PRIu16"]=\"",
+		    msize, slen);
+		fwrite(d, 1, slen, stdout);
+		printf("\"");
+
+		break;
+
+	case Rattach:
+		pp_qid(d, len);
+		break;
+
+	case Rclunk:
+	case Rflush:
+	case Rremove:
+		if (len != 0)
+			printf("invalid %s: %"PRIu32" extra bytes", 
+			    pp_msg_type(type), len);
+		break;
+
+	case Rwalk:
+		if (len < 2) {
+			printf("invaild Rwalk: less than two bytes (%d)",
+			    (int)len);
+			break;
+		}
+
+		memcpy(&slen, d, sizeof(slen));
+		d += sizeof(slen);
+		len -= sizeof(slen);
+		slen = le16toh(slen);
+
+		if (len != QIDSIZE * slen) {
+			printf("invalid Rwalk: wanted %d bytes for %d qids "
+			    "but got %"PRIu32" bytes instead",
+			    QIDSIZE*slen, slen, len);
+			break;
+		}
+
+		printf("nwqid=%"PRIu16, slen);
+
+		for (; slen != 0; slen--) {
+			printf(" ");
+			pp_qid(d, len);
+			d += QIDSIZE;
+			len -= QIDSIZE;
+		}
+
+		break;
+
+	case Ropen:
+	case Rcreate:
+		if (len != QIDSIZE + 4) {
+			printf("invalid %s: expected %d bytes; "
+			    "got %u\n", pp_msg_type(type), QIDSIZE + 4, len);
+			break;
+		}
+
+		pp_qid(d, len);
+		d += QIDSIZE;
+		len -= QIDSIZE;
+
+		memcpy(&iounit, d, sizeof(iounit));
+		d += sizeof(iounit);
+		len -= sizeof(iounit);
+		iounit = le32toh(iounit);
+		printf(" iounit=%"PRIu32, iounit);
+		break;
+
+	case Rread:
+		if (len < sizeof(count)) {
+			printf("invalid Rread: expected %zu bytes at least; "
+			    "got %u\n", sizeof(count), len);
+			break;
+		}
+
+		memcpy(&count, d, sizeof(count));
+		d += sizeof(count);
+		len -= sizeof(count);
+		count = le32toh(count);
+
+		if (len != count) {
+			printf("invalid Rread: expected %d data bytes; "
+			    "got %u\n", count, len);
+			break;
+		}
+
+		/* allocates three extra bytes, oh well... */
+		if ((v = calloc(count + 1, 4)) == NULL)
+			fatal("calloc");
+		strvisx(v, d, count, VIS_SAFE | VIS_TAB | VIS_NL | VIS_CSTYLE);
+		printf("data=%s", v);
+		free(v);
+
+		break;
+
+	case Rwrite:
+		if (len != sizeof(count)) {
+			printf("invalid Rwrite: expected %zu data bytes; "
+			    "got %u\n", sizeof(count), len);
+			break;
+		}
+
+		memcpy(&count, d, sizeof(count));
+		d += sizeof(count);
+		len -= sizeof(count);
+		count = le32toh(count);
+
+		printf("count=%d", count);
+		break;
+
+	case Rerror:
+		memcpy(&slen, d, sizeof(slen));
+		d += sizeof(slen);
+		len -= sizeof(slen);
+		slen = le16toh(slen);
+
+		if (slen != len) {
+			printf("invalid: error string length doesn't "
+			    "match.  Got %d; want %d", slen, len);
+			break;
+		}
+
+		printf("error=\"");
+		fwrite(d, 1, slen, stdout);
+		printf("\"");
+
+		break;
+
+	default:
+		if ((v = calloc(len + 1, 4)) == NULL)
+			fatal("calloc");
+		strvisx(v, d, len, VIS_SAFE | VIS_TAB | VIS_NL | VIS_CSTYLE);
+		printf("body=%s", v);
+		free(v);
+	}
+
+	printf("\n");
+}
+
+static void
+handle_9p(const uint8_t *data, size_t size)
+{
+        uint32_t len;
+	uint16_t tag;
+	uint8_t type;
+
+	assert(size >= HEADERSIZE);
+
+	memcpy(&len, data, sizeof(len));
+	data += sizeof(len);
+
+	memcpy(&type, data, sizeof(type));
+	data += sizeof(type);
+
+	memcpy(&tag, data, sizeof(tag));
+	data += sizeof(tag);
+
+	len = le32toh(len);
+	/* type is one byte long, no endianness issues */
+	tag = le16toh(tag);
+
+	clr();
+	pp_msg(len, type, tag, data);
+	prompt();
+}
+
+static void
+clr(void)
+{
+	printf("\r");
+	fflush(stdout);
+}
+
+static void
+prompt(void)
+{
+	printf("%s", PROMPT);
+	fflush(stdout);
+}
+
+int
+main(int argc, char **argv)
+{
+	int		 ch, sock, handshake;
+	struct event	 ev_sigint, ev_sigterm;
+
+	signal(SIGPIPE, SIG_IGN);
+
+	while ((ch = getopt(argc, argv, "C:cH:hK:P:v")) != -1) {
+		switch (ch) {
+		case 'C':
+			crtpath = optarg;
+			break;
+		case 'c':
+			tls = 1;
+			break;
+		case 'H':
+			host = optarg;
+			break;
+		case 'h':
+			usage(0);
+			break;
+		case 'K':
+			keypath = optarg;
+			break;
+		case 'P':
+			port = optarg;
+			break;
+		case 'v':
+			verbose = 1;
+			break;
+		default:
+			usage(1);
+		}
+	}
+
+	if (host == NULL)
+		host = "localhost";
+	if (port == NULL)
+		port = "1337";
+
+	argc -= optind;
+	argv += optind;
+
+	if (argc != 0)
+		usage(1);
+	/* if (!tls || (crtpath != NULL || keypath != NULL)) */
+		/* usage(1); */
+	if (!tls)
+                errx(1, "must enable tls (for now)");
+
+	log_init(1, LOG_DAEMON);
+	log_setverbose(verbose);
+	log_procinit(getprogname());
+
+	if ((tlsconf = tls_config_new()) == NULL)
+		fatalx("tls_config_new");
+	tls_config_insecure_noverifycert(tlsconf);
+	tls_config_insecure_noverifyname(tlsconf);
+	if (tls_config_set_keypair_file(tlsconf, crtpath, keypath) == -1)
+		fatalx("can't load certs (%s, %s)", crtpath, keypath);
+
+	if ((ctx = tls_client()) == NULL)
+		fatal("tls_client");
+	if (tls_configure(ctx, tlsconf) == -1)
+		fatalx("tls_configure: %s", tls_error(ctx));
+
+	log_info("connecting to %s:%s...", host, port);
+
+	if ((sock = openconn()) == -1)
+		fatalx("can't connect to %s:%s", host, port);
+
+	if (tls_connect_socket(ctx, sock, host) == -1)
+		fatalx("tls_connect_socket: %s", tls_error(ctx));
+
+	for (handshake = 0; !handshake;) {
+		switch (tls_handshake(ctx)) {
+		case -1:
+			fatalx("tls_handshake: %s", tls_error(ctx));
+		case 0:
+			handshake = 1;
+			break;
+		}
+	}
+
+	log_info("connected!");
+
+	mark_nonblock(sock);
+
+	event_init();
+
+	/* initialize global evb */
+	if ((evb = evbuffer_new()) == NULL)
+		fatal("evbuffer_new");
+
+	signal_set(&ev_sigint, SIGINT, sig_handler, NULL);
+	signal_set(&ev_sigterm, SIGINT, sig_handler, NULL);
+
+	signal_add(&ev_sigint, NULL);
+	signal_add(&ev_sigterm, NULL);
+
+	bev = bufferevent_new(sock, client_read, client_write, client_error,
+	    NULL);
+	if (bev == NULL)
+		fatal("bufferevent_new");
+
+	/* setup tls/io */
+	event_set(&bev->ev_read, sock, EV_READ, tls_readcb, bev);
+	event_set(&bev->ev_write, sock, EV_WRITE, tls_writecb, bev);
+
+	bufferevent_enable(bev, EV_READ|EV_WRITE);
+
+	mark_nonblock(0);
+	inbev = bufferevent_new(0, repl_read, NULL, repl_error, NULL);
+	bufferevent_enable(inbev, EV_READ);
+
+	prompt();
+	event_dispatch();
+
+	bufferevent_free(bev);
+	tls_free(ctx);
+	tls_config_free(tlsconf);
+	close(sock);
+
+	return 0;
+}
blob - 84b60242793c3a21ddc50fae638a208fb4f5ee29 (mode 644)
blob + /dev/null
--- compat.h
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef COMPAT_H
-#define COMPAT_H
-
-#include "config.h"
-
-#include <sys/types.h>
-#include <sys/uio.h>
-
-#include <stdarg.h>
-#include <stddef.h>
-#include <stdint.h>
-
-#ifndef __dead
-#define __dead __attribute__((noreturn))
-#endif
-
-#if HAVE_EVENT2
-# include <event2/event.h>
-# include <event2/event_compat.h>
-# include <event2/event_struct.h>
-# include <event2/buffer.h>
-# include <event2/buffer_compat.h>
-# include <event2/bufferevent.h>
-# include <event2/bufferevent_struct.h>
-# include <event2/bufferevent_compat.h>
-#else
-# include <event.h>
-#endif
-
-#ifdef HAVE_QUEUE_H
-# include <sys/queue.h>
-#else
-# include "compat/queue.h"
-#endif
-
-#ifdef HAVE_SYS_TREE_H
-# include <sys/tree.h>
-#else
-# include "compat/tree.h"
-#endif
-
-#ifdef HAVE_LIBUTIL
-# include <imsg.h>
-# include <ohash.h>
-# include <util.h>
-#else
-# include "compat/imsg.h"
-# include "compat/ohash.h"
-# define FMT_SCALED_STRSIZE	7 /* minus sign, 4 digits, suffix, NUL */
-int	fmt_scaled(long long, char *);
-#endif
-
-#ifndef HAVE_ARC4RANDOM
-# include <stdint.h>
-uint32_t	 arc4random(void);
-void		 arc4random_buf(void *, size_t);
-uint32_t	 arc4random_uniform(uint32_t);
-#endif
-
-#ifndef HAVE_ASPRINTF
-int		 asprintf(char **, const char *, ...);
-int		 vasprintf(char **, const char *, ...);
-#endif
-
-#ifndef HAVE_ERR
-void		 err(int, const char *, ...);
-void		 errx(int, const char *, ...);
-void		 warn(int, const char *, ...);
-void		 warnx(int, const char *, ...);
-#else
-#include <err.h>
-#endif
-
-#ifndef FREEZERO
-void		 freezero(void *, size_t);
-#endif
-
-#ifndef HAVE_GETDTABLECOUNT
-int		 getdtablecount(void);
-#endif
-
-#ifndef HAVE_GETDTABLESIZE
-int		 getdtablesize(void);
-#endif
-
-#ifndef HAVE_GETPROGNAME
-const char	*getprogname(void);
-#endif
-
-#ifndef HAVE_MEMMEM
-void		*memmem(const void *, size_t, const void *, size_t);
-#endif
-
-#ifndef HAVE_RECALLOCARRAY
-void		*recallocarray(void *, size_t, size_t, size_t);
-#endif
-
-#ifndef HAVE_SETPROCTITLE
-void		 setproctitle(const char *, ...);
-#endif
-
-#ifndef HAVE_SETPROGNAME
-void		 setprogname(const char *);
-#endif
-
-#ifndef HAVE_STRLCAT
-size_t		 strlcat(char *, const char *, size_t);
-#endif
-
-#ifndef HAVE_STRLCPY
-size_t		 strlcpy(char *, const char *, size_t);
-#endif
-
-#ifndef HAVE_STRSEP
-char		*strsep(char **, const char *);
-#endif
-
-#ifndef HAVE_STRTONUM
-long long	 strtonum(const char *, long long, long long, const char **);
-#endif
-
-#ifdef HAVE_VIS
-# include <vis.h>
-#else
-# include "compat/vis.h"
-#endif
-
-#endif	/* COMPAT_H */
blob - /dev/null
blob + 41698f3563582a33b0dcd1b46aac1564ca3e56c0 (mode 644)
--- /dev/null
+++ lib/9pclib.c
@@ -0,0 +1,251 @@
+/*
+ * 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 <endian.h>
+#include <event.h>
+#include <inttypes.h>
+#include <string.h>
+
+#include "9pclib.h"
+#include "kami.h"
+#include "log.h"
+#include "utils.h"
+
+uint16_t iota_tag;
+
+struct evbuffer *evb;
+
+void
+write_hdr(uint32_t len, uint8_t type, uint16_t tag)
+{
+	len += HEADERSIZE;
+
+	log_debug("enqueuing a packet; len=%"PRIu32" type=%d[%s] tag=%d",
+	    len, type, pp_msg_type(type), tag);
+
+	len = htole32(len);
+	/* type is one byte, no endiannes issues */
+	tag = htole16(tag);
+
+	evbuffer_add(evb, &len, sizeof(len));
+	evbuffer_add(evb, &type, sizeof(type));
+	evbuffer_add(evb, &tag, sizeof(tag));
+}
+
+void
+write_hdr_auto(uint32_t len, uint8_t type)
+{
+	if (++iota_tag == NOTAG)
+		++iota_tag;
+	write_hdr(len, type, iota_tag);
+}
+
+void
+write_str(uint16_t len, const char *str)
+{
+	uint16_t l = len;
+
+	len = htole16(len);
+	evbuffer_add(evb, &len, sizeof(len));
+	evbuffer_add(evb, str, l);
+}
+
+void
+write_str_auto(const char *str)
+{
+	write_str(strlen(str), str);
+}
+
+void
+write_buf(const void *d, uint32_t len)
+{
+	write_32(len);
+	evbuffer_add(evb, d, len);
+}
+
+void
+write_64(uint64_t x)
+{
+	x = htole64(x);
+	evbuffer_add(evb, &x, sizeof(x));
+}
+
+void
+write_32(uint32_t fid)
+{
+	fid = htole32(fid);
+	evbuffer_add(evb, &fid, sizeof(fid));
+}
+
+void
+write_16(uint16_t tag)
+{
+	tag = htole16(tag);
+	evbuffer_add(evb, &tag, sizeof(tag));
+}
+
+void
+write_8(uint8_t x)
+{
+	evbuffer_add(evb, &x, sizeof(x));
+}
+
+
+
+void
+tversion(const char *v, uint32_t msize)
+{
+	uint32_t	len;
+	uint16_t	sl;
+
+	sl = strlen(v);
+
+	/* msize[4] version[s] */
+	len = sizeof(msize) + sizeof(sl) + sl;
+	write_hdr(len, Tversion, NOTAG);
+	write_32(msize);
+	write_str(sl, v);
+}
+
+void
+tattach(uint32_t fid, uint32_t afid, const char *uname, const char *aname)
+{
+	uint32_t	len;
+	uint16_t	ul, al;
+
+	ul = strlen(uname);
+	al = strlen(aname);
+
+	/* fid[4] afid[4] uname[s] aname[s] */
+	len = sizeof(fid) + sizeof(afid) + sizeof(ul) + ul
+	    + sizeof(al) + al;
+	write_hdr_auto(len, Tattach);
+	write_fid(fid);
+	write_fid(afid);
+	write_str(ul, uname);
+	write_str(al, aname);
+}
+
+void
+tclunk(uint32_t fid)
+{
+	uint32_t	len;
+
+	/* fid[4] */
+	len = sizeof(fid);
+	write_hdr_auto(len, Tclunk);
+	write_fid(fid);
+}
+
+void
+tflush(uint16_t oldtag)
+{
+	uint32_t	len;
+
+	/* oldtag[2] */
+	len = sizeof(oldtag);
+	write_hdr_auto(len, Tflush);
+	write_tag(oldtag);
+}
+
+void
+twalk(uint32_t fid, uint32_t newfid, const char **wnames, size_t nwname)
+{
+	size_t		i;
+	uint32_t	len;
+
+	/* fid[4] newfid[4] nwname[2] nwname*(wname[s]) */
+	len = sizeof(fid) + sizeof(newfid) + 2;
+	for (i = 0; i < nwname; ++i)
+		len += 2 + strlen(wnames[i]);
+
+	write_hdr_auto(len, Twalk);
+	write_fid(fid);
+	write_fid(newfid);
+	write_16(nwname);
+	for (i = 0; i < nwname; ++i)
+		write_str_auto(wnames[i]);
+}
+
+void
+topen(uint32_t fid, uint8_t mode)
+{
+	uint32_t	len;
+
+	/* fid[4] mode[1] */
+	len = sizeof(fid) + sizeof(mode);
+	write_hdr_auto(len, Topen);
+	write_fid(fid);
+	write_8(mode);
+}
+
+void
+tcreate(uint32_t fid, const char *name, uint32_t perm, uint8_t mode)
+{
+	uint32_t	len;
+	uint16_t	nl;
+
+	/* fid[4] name[s] perm[4] mode[1] */
+	nl = strlen(name);
+	len = sizeof(fid) + sizeof(nl) + nl + sizeof(perm) + sizeof(mode);
+	write_hdr_auto(len, Tcreate);
+	write_fid(fid);
+	write_str(nl, name);
+	write_32(perm);
+	write_8(mode);
+}
+
+void
+tread(uint32_t fid, uint64_t off, uint32_t count)
+{
+	uint32_t	len;
+
+	/* fid[4] off[8] count[4] */
+	len = sizeof(fid) + sizeof(off) + sizeof(count);
+	write_hdr_auto(len, Tread);
+	write_fid(fid);
+	write_off(off);
+	write_32(count);
+}
+
+void
+twrite(uint32_t fid, uint64_t off, const void *data, uint32_t count)
+{
+	uint32_t	len;
+
+	/* fid[4] off[8] count[4] data[count] */
+	len = sizeof(fid) + sizeof(off) + sizeof(count) + count;
+	write_hdr_auto(len, Twrite);
+	write_fid(fid);
+	write_off(off);
+	write_buf(data, count);
+}
+
+void
+tstat(uint32_t fid)
+{
+	/* fid[4] */
+	write_hdr_auto(sizeof(fid), Tstat);
+	write_fid(fid);
+}
+
+void
+tremove(uint32_t fid)
+{
+	/* fid[4] */
+	write_hdr_auto(sizeof(fid), Tremove);
+	write_fid(fid);
+}
blob - /dev/null
blob + 9952a8b55f1c826652ef1bd543c9218344942d47 (mode 644)
--- /dev/null
+++ lib/9pclib.h
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+/* 9p client library */
+
+#ifndef NPCLIB_H
+#define NPCLIB_H
+
+#include <stdint.h>
+
+extern uint16_t		 iota_tag;
+extern struct evbuffer	*evb;
+
+void		 write_hdr(uint32_t, uint8_t, uint16_t);
+void		 write_hdr_auto(uint32_t, uint8_t);
+void		 write_str(uint16_t, const char *);
+void		 write_str_auto(const char *);
+void		 write_buf(const void *, uint32_t);
+void		 write_64(uint64_t);
+void		 write_32(uint32_t);
+void		 write_16(uint16_t);
+void		 write_8(uint8_t);
+
+#define write_off write_64
+#define write_fid write_32
+#define write_tag write_16
+
+void		 tversion(const char *, uint32_t);
+void		 tattach(uint32_t, uint32_t, const char *, const char *);
+void		 tclunk(uint32_t);
+void		 tflush(uint16_t);
+void		 twalk(uint32_t, uint32_t, const char **, size_t);
+void		 topen(uint32_t, uint8_t);
+void		 tcreate(uint32_t, const char *, uint32_t, uint8_t);
+void		 tread(uint32_t, uint64_t, uint32_t);
+void		 twrite(uint32_t, uint64_t, const void *, uint32_t);
+void		 tstat(uint32_t);
+void		 tremove(uint32_t);
+
+#endif
blob - /dev/null
blob + 196bf03630b229bbf3afdd96ac209990a24c3387 (mode 644)
--- /dev/null
+++ lib/Makefile
@@ -0,0 +1,4 @@
+# simple Makefile stub to make "tags" target work
+SRCS = *.c
+
+.include <bsd.lib.mk>
blob - /dev/null
blob + bd12b3ea1fd8a67b4f3c88dd48ea53ae701669ea (mode 644)
--- /dev/null
+++ lib/kami.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2021, 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.
+ */
+
+#ifndef KAMI_H
+#define KAMI_H
+
+/*
+ * 9p message header.
+ *
+ * The message itself is len bytes long (counting the whole header
+ * too.)
+ */
+struct np_msg_header {
+	uint32_t	len;
+	uint8_t		type;
+	uint16_t	tag;
+};
+
+struct qid {
+	uint64_t		 path;
+	uint32_t		 vers;
+	uint8_t			 type;
+};
+
+/* useful constants */
+#define HEADERSIZE	(4 + 1 + 2)
+#define	VERSION9P	"9P2000"
+#define MSIZE9P		((uint32_t)4*1024*1024)
+#define NOTAG		((uint16_t)~0U)
+#define NOFID		((uint32_t)~0U)
+#define NOUID		(-1)
+#define QIDSIZE		13
+#define MAXWELEM	16
+
+#define NPSTATSIZ(namlen, uidnam, gidnam, unam) \
+	(6 + QIDSIZE + 20 + 2 + namlen + 2 + uidnam + 2 + gidnam + 2 + unam)
+
+/* bits in Qid.type */
+#define QTDIR		0x80		/* type bit for directories */
+#define QTAPPEND	0x40		/* type bit for append only files */
+#define QTEXCL		0x20		/* type bit for exclusive use files */
+#define QTMOUNT		0x10		/* type bit for mounted channel */
+#define QTAUTH		0x08		/* type bit for authentication file */
+#define QTTMP		0x04		/* type bit for non-backed-up file */
+#define QTSYMLINK	0x02		/* type bit for symbolic link */
+#define QTFILE		0x00		/* type bits for plain file */
+
+/* Topen mode/flags */
+#define KOREAD		0x00
+#define KOWRITE		0x01
+#define KORDWR		0x02
+#define KOEXEC		0x03
+#define KOTRUNC		0x10
+#define KORCLOSE	0x40
+
+/* 9p message types */
+enum {
+	Treaddir =	40,	/* .L */
+	Rreaddir,
+
+	Tversion =	100,
+	Rversion,
+	Tauth =		102,
+	Rauth,
+	Tattach =	104,
+	Rattach,
+	Terror =	106,	/* illegal */
+	Rerror,
+	Tflush =	108,
+	Rflush,
+	Twalk =		110,
+	Rwalk,
+	Topen =		112,
+	Ropen,
+	Tcreate =	114,
+	Rcreate,
+	Tread =		116,
+	Rread,
+	Twrite =	118,
+	Rwrite,
+	Tclunk =	120,
+	Rclunk,
+	Tremove =	122,
+	Rremove,
+	Tstat =		124,
+	Rstat,
+	Twstat =	126,
+	Rwstat,
+	Tmax,
+
+	/*
+	 * plan9ports' include/fcall.h also has a
+	 *
+	 *	Topenfd = 98,
+	 *	Ropenfd,
+	 *
+	 * which it's not mentioned in the 9p "rfc" over at
+	 * 9p.cat-v.org.  Ignoring that for now.
+	 */
+};
+
+#endif
blob - /dev/null
blob + 362b9c4cbfe5998442528d99f3faaa2315f44972 (mode 644)
--- /dev/null
+++ lib/log.c
@@ -0,0 +1,196 @@
+/*
+ * 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: %s\n", log_procname, fmt) == -1) {
+			fprintf(stderr, "%s: ", log_procname);
+			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 + 408dec92e050085f6a48c675e9cb0dde00869cd4 (mode 644)
--- /dev/null
+++ lib/log.h
@@ -0,0 +1,44 @@
+/*
+ * 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>
+#include <sys/cdefs.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 + cf1ac0e9438622950da2943dd9476e842bc8531c (mode 644)
--- /dev/null
+++ lib/sandbox.c
@@ -0,0 +1,64 @@
+/*
+ * 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 "log.h"
+#include "sandbox.h"
+
+#ifdef __OpenBSD__
+
+#include <unistd.h>
+
+void
+sandbox_main(void)
+{
+	return;
+}
+
+void
+sandbox_listener(void)
+{
+	return;
+}
+
+void
+sandbox_client(void)
+{
+	return;
+}
+
+#else
+#warning "No sandbox available for this OS"
+
+void
+sandbox_main(void)
+{
+	log_warnx("No sandbox available for this os");
+	return;
+}
+
+void
+sandbox_listener(void)
+{
+	return;
+}
+
+void
+sandbox_client(void)
+{
+	return;
+}
+
+#endif
blob - /dev/null
blob + c964b983ea10e3af850adc627f269bd1051a2619 (mode 644)
--- /dev/null
+++ lib/sandbox.h
@@ -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.
+ */
+
+#ifndef SANDBOX_H
+#define SANDBOX_H
+
+void	sandbox_main(void);
+void	sandbox_listener(void);
+void	sandbox_client(void);
+
+#endif
blob - /dev/null
blob + 19c5accb28cf9b5a472ffc64462f0fd1e645a970 (mode 644)
--- /dev/null
+++ lib/utils.c
@@ -0,0 +1,206 @@
+/*
+ * 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/queue.h>
+#include <sys/uio.h>
+
+#include <event.h>
+#include <ctype.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <imsg.h>
+
+#include "kami.h"
+#include "log.h"
+#include "utils.h"
+
+void *
+xmalloc(size_t size)
+{
+	void	*r;
+
+	if ((r = malloc(size)) == NULL)
+		fatal("malloc");
+	return r;
+}
+
+void *
+xcalloc(size_t nmemb, size_t size)
+{
+	void	*r;
+
+	if ((r = calloc(nmemb, size)) == NULL)
+		fatal("calloc");
+	return r;
+}
+
+char *
+xstrdup(const char *s)
+{
+	char	*r;
+
+	if ((r = strdup(s)) == NULL)
+		fatal("strdup");
+	return r;
+}
+
+void *
+xmemdup(const void *d, size_t len)
+{
+	void	*r;
+
+	if ((r = malloc(len)) == NULL)
+		fatal("malloc");
+	memcpy(r, d, len);
+	return r;
+}
+
+const char *
+pp_msg_type(uint8_t type)
+{
+	switch (type) {
+	case Tversion:	return "Tversion";
+	case Rversion:	return "Rversion";
+	case Tauth:	return "Tauth";
+	case Rauth:	return "Rauth";
+	case Tattach:	return "Tattach";
+	case Rattach:	return "Rattach";
+	case Terror:	return "Terror"; /* illegal */
+	case Rerror:	return "Rerror";
+	case Tflush:	return "Tflush";
+	case Rflush:	return "Rflush";
+	case Twalk:	return "Twalk";
+	case Rwalk:	return "Rwalk";
+	case Topen:	return "Topen";
+	case Ropen:	return "Ropen";
+	case Tcreate:	return "Tcreate";
+	case Rcreate:	return "Rcreate";
+	case Tread:	return "Tread";
+	case Rread:	return "Rread";
+	case Twrite:	return "Twrite";
+	case Rwrite:	return "Rwrite";
+	case Tclunk:	return "Tclunk";
+	case Rclunk:	return "Rclunk";
+	case Tremove:	return "Tremove";
+	case Rremove:	return "Rremove";
+	case Tstat:	return "Tstat";
+	case Rstat:	return "Rstat";
+	case Twstat:	return "Twstat";
+	case Rwstat:	return "Rwstat";
+	default:	return "unknown";
+	}
+}
+
+const char *
+pp_qid_type(uint8_t type)
+{
+	switch (type) {
+	case QTDIR:     return "dir";
+	case QTAPPEND:  return "append-only";
+	case QTEXCL:    return "exclusive";
+	case QTMOUNT:   return "mounted-channel";
+	case QTAUTH:    return "authentication";
+	case QTTMP:     return "non-backed-up";
+	case QTSYMLINK: return "symlink";
+	case QTFILE:    return "file";
+	}
+
+	return "unknown";
+}
+
+static void
+hexdump_ppline(int x, uint8_t *data, size_t len)
+{
+	for (; x < 50; x++)
+		printf(" ");
+
+	printf("|");
+
+	for (x = 0; x < (int)len; ++x) {
+		if (isgraph(data[x]))
+			printf("%c", data[x]);
+		else
+			printf(".");
+	}
+
+	printf("|\n");
+}
+
+void
+hexdump(const char *label, uint8_t *data, size_t len)
+{
+	size_t	i;
+	int	x, n;
+
+	/*
+	 * Layout:
+	 * === first block === == second block ==  |........|\n
+	 * first and second block are 8 bytes long (for a total of 48
+	 * columns), plus two separator plus two | plus 16 chars, for
+	 * a total of 68 characters.
+	 */
+
+	printf("\nhexdump \"%s\": (%zu bytes)\n", label, len);
+	for (x = 0, n = 0, i = 0; i < len; ++i) {
+		if (i != 0 && i % 8 == 0) {
+			printf(" ");
+			x++;
+		}
+
+		if (n == 16) {
+			hexdump_ppline(x, &data[i - 16], 16);
+			x = 0;
+			n = 0;
+		}
+
+		printf("%02x ", data[i]);
+		x += 3;
+		n++;
+	}
+
+	if (n != 0)
+                hexdump_ppline(x, &data[i - n], n);
+
+	printf("\n");
+}
+
+void
+imsg_event_add(struct imsgev *iev)
+{
+	iev->events = EV_READ;
+	if (iev->ibuf.w.queued)
+		iev->events |= EV_WRITE;
+
+	event_del(&iev->ev);
+	event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev);
+	event_add(&iev->ev, NULL);
+}
+
+int
+imsg_compose_event(struct imsgev *iev, uint16_t type, uint32_t peerid,
+    pid_t pid, int fd, const void *data, uint16_t datalen)
+{
+	int	ret;
+
+	if ((ret = imsg_compose(&iev->ibuf, type, peerid, pid, fd, data,
+	    datalen) != -1))
+		imsg_event_add(iev);
+
+	return ret;
+}
blob - /dev/null
blob + be7be3e1e7d43208a380f99283ed303709a37d0f (mode 644)
--- /dev/null
+++ lib/utils.h
@@ -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.
+ */
+
+#ifndef UTILS_H
+#define UTILS_H
+
+#include <imsg.h>
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+
+struct imsgev {
+	struct imsgbuf	 ibuf;
+	void		(*handler)(int, short, void *);
+	struct event	 ev;
+	short		 events;
+};
+
+void	*xmalloc(size_t);
+void	*xcalloc(size_t, size_t);
+char	*xstrdup(const char *);
+void	*xmemdup(const void *, size_t);
+
+const char	*pp_msg_type(uint8_t);
+const char	*pp_qid_type(uint8_t);
+
+void		hexdump(const char *, uint8_t *data, size_t len);
+
+void	imsg_event_add(struct imsgev *);
+int	imsg_compose_event(struct imsgev *, uint16_t, uint32_t, pid_t,
+	    int, const void *, uint16_t);
+
+#endif
blob - ba451bdd0d365e2c95e1d8bebcf0ee850163c8e7 (mode 644)
blob + /dev/null
--- configure.ac
+++ /dev/null
@@ -1,160 +0,0 @@
-AC_INIT([kamid], [0.1], [kamid@omarpolo.com], [kamid], [gemini://kamid.omarpolo.com])
-AC_CONFIG_LIBOBJ_DIR(compat)
-AC_CANONICAL_HOST
-AM_INIT_AUTOMAKE([-Wall foreign subdir-objects])
-AC_PROG_CC
-AC_USE_SYSTEM_EXTENSIONS
-AC_PROG_YACC
-
-PKG_PROG_PKG_CONFIG
-
-# Some functions can be in libbsd.  Thanks to lldpb for the inspiration :)
-AC_ARG_WITH([libbsd],
-  AS_HELP_STRING([--with-libbsd], [Use libbsd @<:@default=auto@:>@]),
-  [],
-  [with_libbsd=auto])
-if test x"$with_libbsd" != x"no"; then
-  PKG_CHECK_MODULES([libbsd], [libbsd-overlay libbsd-ctor], [
-    _save_AM_CFLAGS="$AM_CFLAGS"
-    _save_LIBS="$LIBS"
-    AM_CFLAGS="$AM_CFLAGS $libbsd_CFLAGS"
-    LIBS="$LIBS $libbsd_LIBS"
-    AC_MSG_CHECKING([if libbsd can be linked correctly])
-    AC_LINK_IFELSE([AC_LANG_PROGRAM([[
-  @%:@include <sys/time.h>
-  @%:@include <sys/types.h>
-  ]], [[]])],[
-        AC_MSG_RESULT(yes)
-        AM_CFLAGS="$AM_CFLAGS $libbsd_CFLAGS"
-        AM_LDFLAGS="$AM_LDFLAGS $libbsd_LIBS"
-        with_libbsd=yes
-      ],[
-        AC_MSG_RESULT(no)
-        AM_CFLAGS="$_save_AM_CFLAGS"
-        LIBS="$_save_LIBS"
-        if test x"$with_libbsd" = x"yes"; then
-           AC_MSG_FAILURE([*** no libbsd support found])
-        fi
-        with_libbsd=no
-      ])
-  ], [
-    if test x"$with_libbsd" = x"yes"; then
-       AC_MSG_FAILURE([*** no libbsd support found])
-    fi
-    with_libbsd=no
-  ])
-fi
-
-AC_SEARCH_LIBS([arc4random], [],
-	[AC_DEFINE([HAVE_ARC4RANDOM], 1, [arc4random])],
-	[AC_DEFINE([HAVE_ARC4RANDOM], 0, [arc4random])])
-
-AC_REPLACE_FUNCS([
-	asprintf	\
-	err		\
-	freezero	\
-	getdtablecount	\
-	getdtablesize	\
-	getprogname	\
-	memmem		\
-	recallocarray	\
-	setproctitle	\
-	setprogname	\
-	strlcat		\
-	strlcpy		\
-	strsep		\
-	strtonum	\
-	vis		\
-])
-
-AC_MSG_CHECKING([for sys/queue.h with TAILQ_FOREACH_SAFE and STAILQ_ENTRY])
-AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
-#include <sys/queue.h>
-#include <stddef.h>
-], [
-	TAILQ_HEAD(tailhead, entry) head;
-	struct entry {
-		TAILQ_ENTRY(entry) entries;
-	} *np, *nt;
-	TAILQ_INIT(&head);
-	TAILQ_FOREACH_SAFE(np, &head, entries, nt) {
-		/* nop */ ;
-	}
-
-	STAILQ_HEAD(listhead, qentry) qhead = STAILQ_HEAD_INITIALIZER(qhead);
-	struct qentry {
-		STAILQ_ENTRY(qentry) entries;
-	} foo;
-
-	return 0;
-])], [
-	AC_MSG_RESULT(yes)
-	AC_DEFINE([HAVE_QUEUE_H], 1, [QUEUE_H])
-], AC_MSG_RESULT(no))
-
-AC_CHECK_HEADERS([sys/tree.h])
-
-AC_CHECK_DECL(PR_SET_NAME, AC_DEFINE([HAVE_PR_SET_NAME], 1, [pr_set_name]), [],
-	[[#include <sys/prctl.h>]])
-
-AC_CHECK_LIB([crypto], [RAND_add], [], [
-	AC_MSG_ERROR([requires openssl])
-])
-
-AC_CHECK_LIB(tls, tls_init, [], [
-	AC_MSG_ERROR([requires libtls])
-])
-
-# small hack to avoid linking *everything* to readline.
-libs_orig="${LIBS}"
-AC_CHECK_LIB(readline, readline, [], [
-	AC_DEFINE([HAVE_READLINE], 0, [1 if readline is found])
-])
-KAMIFTP_LIBS="${LIBS}"
-LIBS="${libs_orig}"
-AC_SUBST([KAMIFTP_LIBS])
-
-AS_CASE([$host_os],
-	[*openbsd*], [AC_CHECK_LIB([event], [event_init], [],
-			[AC_MSG_ERROR([requires libevent])])],
-	[PKG_CHECK_MODULES([libevent2], [libevent_core >= 2],
-		[
-			AC_DEFINE([HAVE_EVENT2], 1, [1 if using event2])
-			AM_CFLAGS="$libevent2_CFLAGS $AM_CFLAGS"
-			LIBS="$libevent2_LIBS $LIBS"
-		], [AC_MSG_ERROR([requires libevent])])])
-
-AC_CHECK_LIB(util, imsg_init, [], [
-	AC_LIBOBJ(fmt_scaled)
-	AC_LIBOBJ(imsg)
-	AC_LIBOBJ(imsg-buffer)
-	AC_LIBOBJ(ohash)
-])
-
-# check compiler flags
-AC_DEFUN([CC_ADD_CHECK_FLAGS], [
-	AC_MSG_CHECKING([if $CC supports $1 flag])
-	old_AM_CFLAGS="$AM_CFLAGS"
-	AM_CFLAGS="$AM_CFLAGS $1"
-	AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])], [
-		AC_MSG_RESULT(yes)
-	], [
-		AC_MSG_RESULT(no)
-		AM_CFLAGS="$old_AM_CFLAGS"
-	])
-])
-CC_ADD_CHECK_FLAGS([-Wall])
-CC_ADD_CHECK_FLAGS([-Wextra])
-CC_ADD_CHECK_FLAGS([-Wmissing-prototypes])
-CC_ADD_CHECK_FLAGS([-Wstrict-prototypes])
-CC_ADD_CHECK_FLAGS([-Wwrite-strings])
-CC_ADD_CHECK_FLAGS([-Wno-unused-parameter])
-
-AC_SUBST([AM_CFLAGS])
-
-AC_CONFIG_HEADERS([config.h])
-AC_CONFIG_FILES([
-	Makefile
-])
-
-AC_OUTPUT
blob - /dev/null
blob + fb2fec0c8c16141e0cb61f5dcb1150d73bfd9b31 (mode 644)
--- /dev/null
+++ ninepscript/Makefile
@@ -0,0 +1,18 @@
+.PATH:${.CURDIR}/../lib
+.PATH:${.CURDIR}/../kamid
+
+.include "../kamid-version.mk"
+
+PROG=		ninepscript
+SRCS=		client.c log.c parse.y sandbox.c script.c utils.c
+MAN=		ninepscript.5 ninepscript.8
+
+CPPFLAGS=	-I${.CURDIR}/../kamid/ \
+		-I${.CURDIR}/../lib -I${.CURDIR}
+
+LDADD+= -levent -lutil -ltls
+DPADD+= ${LIBEVENT} ${LIBUTIL} ${LIBTLS}
+
+NOMAN = Yes
+
+.include <bsd.prog.mk>
blob - /dev/null
blob + 6da049b0758a48894158408f942e91823baa3ade (mode 644)
--- /dev/null
+++ ninepscript/ninepscript.5
@@ -0,0 +1,265 @@
+.\" 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.
+.\"
+.Dd $Mdocdate: December 02 2021$
+.Dt NINEPSCRIPT 5
+.Os
+.Sh NAME
+.Nm ninepscript
+.Nd kamid regress test scripting language
+.Sh DESCRIPTION
+.Nm
+is a custom DSL
+.Pq domain specific language
+used to write the regression suite of
+.Xr kamid 8 .
+it has a fairly simple and regular syntax that features constant
+declarations, routines, test cases.
+It does not support conditional or loops.
+.Pp
+Additional files can be included with the
+.Ic include
+keyword, for example
+.Bd -literal -offset Ds
+include "lib.9ps"
+.Ed
+.Pp
+Comments can be placed anywhere, start with the # character and extend
+until the end of the line.
+.Pp
+An expression is a fundamental building block.
+It is something that yields a value.
+An expression may be either a:
+.Bl -tag -width variable_reference
+.It literal
+a bare number or string.
+A string is a sequence of characters enclosed in single or double quotes
+.Sq like this
+or
+.Dq like this .
+.It routine call
+Evaluate the routine code and return the value computed by it.
+The syntax is
+.Bd -literal -offset Ds
+.Ar routine Ns Po Ar arguments... Pc
+.Ed
+.Pp
+The
+.Ql ...
+special syntax expands to the list of variable arguments of the
+current routine.
+Be aware that the implementation of the variable arguments is quirky
+and has a lot of corner cases, use with care!
+.It variable reference
+a variable
+.Pq or constant
+reference is the name of a previously defined variable or constant.
+It evaluates to the value of the variable or constant in the current
+scope.
+.It comparison
+The syntax is
+.Bd -literal -offset Ds
+.Ar expression Cm == Ar expression
+.Ar expression Cm <= Ar expression
+.Ed
+.Pp
+and yields a true value if the two expressions are considered to be
+respectively
+.Sq equal
+or
+.Sq lesser equal ,
+a false value otherwise.
+Two values are equal if they are both number and represent the same
+value
+.Pq regardless of the size
+or if they're both the same string.
+.It cast
+convert one value to another type.
+The syntax is
+.Ql Ar expression : Ns Ar type
+where type is one of
+.Sq u8 ,
+.Sq u16 ,
+.Sq u32
+or
+.Sq str .
+.It field access
+Access a field of a complex object.
+The syntax is
+.Ql Ar object . Ns Ar field .
+See the
+.Sx OBJECTS AND FIELDS
+section for the description of objects types and fields allowed.
+.El
+.Pp
+An expression is considered to be
+.Dq false
+if evaluates to a number and its value is zero.
+Otherwise, it's considered to be
+.Dq true .
+.Pp
+The top-level declarations are:
+.Bl -tag -width Ds
+.It Ic const Ar identifier No = Ar value
+Declare
+.Ar identifier
+to be a constant that evaluates to
+.Ar value .
+.Ar value
+must be a literal or a cast from a literal.
+Multiple constant can be declared at the same time using the following
+syntax:
+.Bd -literal -offset Ds
+.Ic const (
+	foo = 5
+	bar = 7
+)
+.Ed
+.Pp
+Note that newlines are mandatory after an
+.Ar identifier No = Ar value
+line in this case.
+.It Ic proc Ar name Ns Po Ar arguments ... Pc Brq code ...
+Define a routine called
+.Ar name
+that accepts the comma-separated list of
+.Ar arguments .
+When a routine is called, its
+.Ar code
+gets evaluated in a lexical scope where
+.Ar arguments
+are defined to the value passed by the caller.
+A routine may be called only within another routine body or inside a
+.Ic testing
+body.
+.It Ic testing Ar reason Ic dir Ar path Brq code ...
+Define a test case.
+.Ar reason
+is what the test block is about and
+.Ar path
+is the path to the root directory where the test will be executed.
+.Ar reason
+and
+.Ar path
+must be string literals.
+.El
+.Pp
+Inside a
+.Ic proc
+or
+.Ic testing
+code block the following instructions are allowed:
+.Bl -tag -width Ds
+.It Ar variable Cm = Ar expression
+Set a local lexical
+.Ar variable
+to the value yielded by
+.Ar expression .
+The
+.Ar variable
+lifetime last from this declaration until the end of the current
+block.
+.It Ar procedure Ns Pq Ar arguments ...
+Execute
+.Ar procedure
+with the given
+.Ar arguments .
+.It Ic assert Ar comparison
+Evaluate
+.Ar comparison
+and if it not yields a true-ish value terminate the current running
+test and mark it as failed.
+Multiple assertion can be done in one single
+.Ic assert
+block using the following syntax:
+.Bd -literal -offset Ds
+.Ic assert (
+	comparison_1
+	comparison_2
+	...
+	comparison_n
+)
+.Ed
+.Pp
+Note that newlines are mandatory after every
+.Ar comparison
+in this case.
+.It Ic should-fail Ar expression Op : Ar reason
+Evaluate
+.Ar expression
+and continue only if the evaluation produced an error.
+If the execution of
+.Ar expression
+is successful, terminate the current test.
+.Ar reason
+is optional and, if present, must be a literal string.
+It is similar to the
+.Sq try-catch
+statement of other programming languages.
+.El
+.Sh BUILT IN FUNCTIONS
+These functions are built into the language and provided by the
+interpreter:
+.Bl -tag -width Ds
+.It Ic debug Ns Po Ar arg, ... Pc
+Print the argument list separated by a space and followed by a newline
+if the interpreter runs with the verbose flag set.
+.It Ic iota Ns Pq
+Return distinct u16 integer every time it's called.
+Starts at zero and goes up to 254 to then wrap around to zero again.
+255 is skipped because
+.Ic iota
+is intended to be used to provide the tag for
+.Ic send
+and 255 is the special
+.Sq NOTAG
+value in 9P200.
+.It Ic print Ns Po Ar arg, ... Pc
+Print the argument list separated by a space and followed by a
+newline.
+.It Ic recv Ns Pq
+Receive a message from the server and return it as an object.
+A
+.Dv Terror
+doesn't stop the execution of the test, rather, an error object is
+returned.
+See
+.Sx OBJECTS AND FIELDS
+for the complete list of objects.
+.It Ic send Ns Po Ar type, tag, ... Pc
+Send a 9P message with the given
+.Ar type
+and
+.Ar tag .
+Other arguments, if given, are packed into the message and sent as
+well, respecting the given order.
+The overall length of the message is computed automatically.
+.It Ic skip Ns Pq
+Terminate the execution of the current test suite immediately.
+The test won't be counted as passed nor failed, but as skipped.
+.El
+.Sh OBJECTS AND FIELDS
+List of objects and fields...
+.Sh SEE ALSO
+.Xr 9p 7 ,
+.Xr kamid 8 ,
+.Xr ninepscript 8
+.Sh AUTHORS
+.An -nosplit
+.Nm
+was designed and implemented by
+.An Omar Polo Aq Mt op@omarpolo.com
+for the
+.Xr kamid 8
+daemon regression suite.
blob - /dev/null
blob + 2df71001f2c0ddff2c4217feba1820fecb17df36 (mode 644)
--- /dev/null
+++ ninepscript/ninepscript.8
@@ -0,0 +1,54 @@
+.\" 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.
+.\"
+.Dd $Mdocdate: December 02 2021$
+.Dt NINEPSCRIPT 8
+.Os
+.Sh NAME
+.Nm ninepscript
+.Nd scripting language for the kamid regress suite
+.Sh SYNOPSIS
+.Nm
+.Op Fl nv
+.Op Fl x Ar pattern
+.Ar
+.Sh DESCRIPTION
+.Nm
+is an interpreter for a custom DSL
+.Pq domain sppecific language
+used to write the regression suite of
+.Xr kamid 8 .
+The test themselves are written in
+.Xr ninepscript 5 .
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl n
+don't run the test, check only the syntax of the provided
+.Ar files .
+.It Fl v
+verbose logging, print more information during the tests execution.
+.It Fl x Ar pattern
+Run only the tests that match the given
+.Ar pattern .
+.El
+.Pp
+.Nm
+first loads all the given
+.Ar files
+then proceeds to execute each defined test.
+.Pp
+See
+.Xr ninepscript 5
+for the description of the ninepscript language.
blob - /dev/null
blob + d8897db51f613efeec14222cf68a621ef949d128 (mode 644)
--- /dev/null
+++ ninepscript/parse.y
@@ -0,0 +1,688 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ * Copyright (c) 2018 Florian Obser <florian@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 <errno.h>
+#include <event.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "log.h"
+#include "kami.h"
+#include "utils.h"
+
+#include "script.h"
+
+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;
+	u_char			*ungetbuf;
+	int			 eof_reached;
+	int			 lineno;
+	int			 errors;
+} *file, *topfile;
+struct file	*pushfile(const char *);
+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);
+
+static int	shouldfail;
+
+typedef struct {
+	union {
+		struct op	*op;
+		struct proc	*proc;
+		char		*str;
+		int64_t		 num;
+	} v;
+	int		 lineno;
+} YYSTYPE;
+
+%}
+
+/*
+ * for bison:
+ * %define parse.error verbose
+ */
+
+%token	ASSERT
+%token	CONST
+%token	DIR
+%token	ERROR
+%token	INCLUDE
+%token	PROC
+%token	REPEAT
+%token	SHOULD_FAIL STR
+%token	TESTING
+%token	U8 U16 U32
+%token	VARGS
+
+%token	<v.str>		STRING SYMBOL
+%token	<v.num>		NUMBER
+
+%type	<v.op>		cast cexpr check expr faccess funcall
+%type	<v.op>		literal sfail var varref vargs
+
+%type	<v.proc>	procname
+
+%%
+
+program : /* empty */
+	| program '\n'
+	| program include '\n'
+	| program const '\n'
+	| program proc '\n'
+	| program test '\n'
+	;
+
+optnl		: '\n' optnl		/* zero or more newlines */
+		| /*empty*/
+		;
+
+nl	: '\n' optnl ;
+
+include : INCLUDE STRING {
+		struct file	*nfile;
+
+		if ((nfile = pushfile($2)) == NULL) {
+			yyerror("failed to include file %s", $2);
+			free($2);
+			YYERROR;
+		}
+		free($2);
+
+		file = nfile;
+		lungetc('\n');
+	}
+	;
+
+const	: CONST consti
+	| CONST '(' optnl mconst ')'
+	;
+
+mconst	: consti nl | mconst consti nl ;
+
+consti	: SYMBOL '=' expr {
+		if (!global_set($1, $3)) {
+			yyerror("can't set %s: illegal expression", $1);
+			free($1);
+			free_op($3);
+			YYERROR;
+		}
+	}
+	;
+
+var	: SYMBOL '=' expr	{ $$ = op_assign($1, $3); }	;
+varref	: SYMBOL		{ $$ = op_var($1); }		;
+literal	: STRING		{ $$ = op_lit_str($1); }
+	| NUMBER		{ $$ = op_lit_num($1); }	;
+
+/*
+ * `expr '=' '=' expr` is ambiguous.  furthermore, we're not
+ * interested in checking all the possibilities here.
+ */
+cexpr	: literal | varref | funcall | faccess ;
+check	: cexpr '=' '=' cexpr	{ $$ = op_cmp_eq($1, $4); }
+	| cexpr '<' '=' cexpr	{ $$ = op_cmp_leq($1, $4); }
+	;
+
+expr	: literal | funcall | varref | check | cast | faccess | vargs ;
+
+vargs	: VARGS		{ $$ = op_vargs(); }			;
+
+cast	: expr ':' U8	{ $$ = op_cast($1, V_U8); }
+	| expr ':' U16	{ $$ = op_cast($1, V_U16); }
+	| expr ':' U32	{ $$ = op_cast($1, V_U32); }
+	| expr ':' STR	{ $$ = op_cast($1, V_STR); }
+	;
+
+faccess	: varref '.' SYMBOL	{ $$ = op_faccess($1, $3); }
+	| faccess '.' SYMBOL	{ $$ = op_faccess($1, $3); }
+	;
+
+procname: SYMBOL {
+		if (($$ = proc_by_name($1)) == NULL) {
+			yyerror("unknown proc %s", $1);
+			free($1);
+			YYERROR;
+		}
+		free($1);
+	}
+	;
+
+funcall	: procname {
+		prepare_funcall();
+	} '(' args optcomma ')' {
+		struct proc	*proc;
+		int		 argc;
+
+		$$ = op_funcall($1);
+		proc = $$->v.funcall.proc;
+		argc = $$->v.funcall.argc;
+
+		if (argc != proc->minargs && !proc->vararg) {
+			yyerror("invalid arity for `%s': want %d arguments "
+			    "but %d given.", $1->name, proc->minargs, argc);
+			/* TODO: recursively free $$ */
+			YYERROR;
+		}
+
+		if (argc < proc->minargs && proc->vararg) {
+			yyerror("invalid arity for `%s': want at least %d "
+			    "arguments but %d given.", $1->name, proc->minargs,
+			    argc);
+			/* TODO: recursively free $$ */
+			YYERROR;
+		}
+	}
+	;
+
+optcomma: /* empty */ | ',' ;
+
+dots	: '.' '.' '.' ;
+
+args	: /* empty */
+	| args ',' expr	{ push_arg($3); }
+	| args ',' dots	{ push_arg(op_rest()); }
+	| expr		{ push_arg($1); }
+	| dots		{ push_arg(op_rest()); }
+	;
+
+proc	: PROC SYMBOL {
+		prepare_proc();
+	} '(' args ')' {
+		if (!proc_setup_body()) {
+			yyerror("invalid argument in proc `%s' definition",
+			    $2);
+			free($2);
+			YYERROR;
+		}
+	} '{' optnl block '}' {
+		proc_done($2);
+	}
+	;
+
+block	: /* empty */
+	| block var nl		{ block_push($2); }
+	| block funcall nl	{ block_push($2); }
+	| block assert nl
+	| block sfail nl	{ block_push($2); }
+	;
+
+sfail	: SHOULD_FAIL expr		{ $$ = op_sfail($2, NULL); }
+	| SHOULD_FAIL expr ':' STRING	{ $$ = op_sfail($2, $4); }
+	;
+
+assert	: ASSERT asserti
+	| ASSERT '(' optnl massert ')'
+	;
+
+massert	: asserti nl | massert asserti nl ;
+
+asserti	: check			{ block_push(op_assert($1)); }
+	;
+
+test	: TESTING STRING DIR STRING {
+		prepare_test();
+	} testopt '{' optnl block '}' {
+		test_done(shouldfail, $2, $4);
+		shouldfail = 0;
+	}
+	;
+
+testopt	: /* empty */
+	| SHOULD_FAIL	{ shouldfail = 1; }
+	;
+
+%%
+
+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)
+		fatalx("yyerror vasprintf");
+	va_end(ap);
+	logit(LOG_CRIT, "%s:%d: %s", 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[] = {
+		{"assert",	ASSERT},
+		{"const",	CONST},
+		{"dir",		DIR},
+		{"include",	INCLUDE},
+		{"proc",	PROC},
+		{"repeat",	REPEAT},
+		{"should-fail",	SHOULD_FAIL},
+		{"str",		STR},
+		{"testing",	TESTING},
+		{"u16",		U16},
+		{"u32",		U32},
+		{"u8",		U8},
+		{"vargs",	VARGS},
+	};
+	const struct keywords	*p;
+
+	p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
+	    sizeof(keywords[0]), kw_cmp);
+
+	if (p)
+		return p->k_val;
+	else
+		return SYMBOL;
+}
+
+#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, next;
+
+	if (quotec) {
+		if ((c = igetc()) == EOF) {
+			yyerror("reached end of file while parsing "
+			    "quoted string");
+			if (file == topfile || popfile() == EOF)
+				return EOF;
+			return quotec;
+		}
+		return c;
+	}
+
+	while ((c = igetc()) == '\\') {
+		next = igetc();
+		if (next != '\n') {
+			c = next;
+			break;
+		}
+		yylval.lineno = file->lineno;
+		file->lineno++;
+	}
+
+	if (c == EOF) {
+		/*
+		 * Fake EOL when hit EOF for the first time. This gets line
+		 * count right if last line in 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, "lungetc");
+		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;
+}
+
+
+#if 0
+int my_yylex(void);
+
+int
+yylex(void)
+{
+	int x;
+
+	switch (x = my_yylex()) {
+	case ASSERT: puts("assert"); break;
+	case CONST: puts("const"); break;
+	case DIR: puts("dir"); break;
+	case ERROR: puts("error"); break;
+	case INCLUDE: puts("include"); break;
+	case PROC: puts("proc"); break;
+	case REPEAT: puts("repeat"); break;
+	case STR: puts(":str"); break;
+	case TESTING: puts("testing"); break;
+	case U8: puts(":u8"); break;
+	case U16: puts(":u16"); break;
+	case U32: puts(":u32"); break;
+
+	case STRING: printf("string \"%s\"\n", yylval.v.str); break;
+	case SYMBOL: printf("symbol %s\n", yylval.v.str); break;
+	case NUMBER: printf("number %"PRIu64"\n", yylval.v.num); break;
+
+	default:
+		printf("character ");
+		if (x == '\n')
+			printf("\\n");
+		else
+			printf("%c", x);
+		printf(" [0x%x]", x);
+		printf("\n");
+		break;
+	}
+
+	return x;
+}
+
+int
+my_yylex(void)
+#else
+int
+yylex(void)
+#endif
+{
+	unsigned char	 buf[8096];
+	unsigned char	*p;
+	int		 quotec, next, c;
+	int		 token;
+
+	p = buf;
+	while ((c = lgetc(0)) == ' ' || c == '\t' || c == '\f')
+		; /* nop */
+
+	yylval.lineno = file->lineno;
+	if (c == '#')
+		while ((c = lgetc(0)) != '\n' && c != EOF)
+			; /* nop */
+
+	switch (c) {
+	case ':':
+		return c;
+		break;
+	case '\'':
+	case '\"':
+		quotec = c;
+		while (1) {
+			if ((c = lgetc(quotec)) == EOF)
+				return 0;
+			if (c == '\n') {
+				file->lineno++;
+				continue;
+			} else if (c == '\\') {
+				if ((next = lgetc(quotec)) == EOF)
+					return 0;
+				if (next == quotec || next == ' ' ||
+				    next == '\t')
+					c = next;
+				else if (next == '\n') {
+					file->lineno++;
+					continue;
+				} else
+					lungetc(next);
+			} else if (c == quotec) {
+				*p = '\0';
+				break;
+			} else if (c == '\0') {
+				yyerror("syntax error");
+				return findeol();
+			}
+
+			if (p + 1 >= buf + sizeof(buf) - 1) {
+				yyerror("string too long");
+				return findeol();
+			}
+
+			*p++ = c;
+		}
+
+		yylval.v.str = xstrdup(buf);
+		return STRING;
+	}
+
+#define allowed_to_end_number(x) \
+	(isspace(x) || x == ')' || x == ',' || x == '/' || x == '}' \
+	    || x == '=' || x == ':')
+
+	if (c == '-' || isdigit(c)) {
+		do {
+			*p++ = c;
+			if ((size_t)(p-buf) >= sizeof(buf)) {
+				yyerror("string too long");
+				return findeol();
+			}
+		} while ((c = lgetc(0)) != EOF && (isdigit(c) || c == 'x'));
+		lungetc(c);
+		if (p == buf + 1 && buf[0] == '-')
+			goto nodigits;
+		if (c == EOF || allowed_to_end_number(c)) {
+			char *ep;
+
+			*p = '\0';
+			errno = 0;
+			yylval.v.num = strtoll(buf, &ep, 0);
+			if (*ep != '\0' || (errno == ERANGE &&
+			    (yylval.v.num == LONG_MAX ||
+			    yylval.v.num == LONG_MIN))) {
+				yyerror("\"%s\" invalid number or out of range",
+				    buf);
+				return findeol();
+			}
+
+			return NUMBER;
+		} else {
+nodigits:
+			while (p > buf + 1)
+				lungetc(*--p);
+			c = *--p;
+			if (c == '-')
+				return c;
+		}
+	}
+
+#define allowed_in_symbol(x)				\
+	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
+	    x != '{' && x != '}' && \
+	    x != '!' && x != '=' && \
+	    x != '#' && x != ',' && \
+	    x != '.' && x != ':'))
+
+	if (isalnum(c) || c == ':' || c == '_') {
+		do {
+			*p++ = c;
+			if ((size_t)(p-buf) >= sizeof(buf)) {
+				yyerror("string too long");
+				return findeol();
+			}
+		} while ((c = lgetc(0)) != EOF && (allowed_in_symbol(c)));
+		lungetc(c);
+		*p = '\0';
+		if ((token = lookup(buf)) == SYMBOL)
+			yylval.v.str = xstrdup(buf);
+		return token;
+	}
+
+	if (c == '\n') {
+		yylval.lineno = file->lineno;
+		file->lineno++;
+	}
+	if (c == EOF)
+		return 0;
+	return c;
+}
+
+struct file *
+pushfile(const char *name)
+{
+	struct file	*nfile;
+
+	if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
+		log_warn("calloc");
+		return NULL;
+	}
+	if ((nfile->name = strdup(name)) == NULL) {
+		log_warn("strdup");
+		free(nfile);
+		return NULL;
+	}
+	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
+		log_warn("%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) {
+		log_warn("malloc");
+		fclose(nfile->stream);
+		free(nfile->name);
+		free(nfile);
+		return NULL;
+	}
+	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;
+}
+
+void
+loadfile(const char *path)
+{
+	int errors;
+
+	file = pushfile(path);
+	if (file == NULL)
+		err(1, "pushfile");
+	topfile = file;
+
+	yyparse();
+	errors = file->errors;
+	popfile();
+
+	if (errors)
+		errx(1, "can't load %s because of errors", path);
+}
blob - /dev/null
blob + c6b2837f20fc239ffd568d478a748fd27272ed11 (mode 644)
--- /dev/null
+++ ninepscript/script.c
@@ -0,0 +1,1771 @@
+/*
+ * 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 <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <assert.h>
+#include <endian.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <poll.h>
+#include <pwd.h>
+#include <regex.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "client.h"
+#include "kami.h"
+#include "kamid.h"
+#include "log.h"
+#include "script.h"
+#include "utils.h"
+
+#define DEBUG 0
+
+#ifndef INFTIM
+#define INFTIM -1
+#endif
+
+static const char	*argv0;
+
+static uint8_t		*lastmsg;
+
+static struct imsgbuf	 ibuf;
+static int		 ibuf_inuse;
+static int		 child_out = -1;
+
+static struct procs procs = TAILQ_HEAD_INITIALIZER(procs);
+static struct tests tests = TAILQ_HEAD_INITIALIZER(tests);
+
+static int ntests;
+
+static struct opstacks blocks = TAILQ_HEAD_INITIALIZER(blocks);
+static struct opstacks args   = TAILQ_HEAD_INITIALIZER(args);
+
+#define STACK_HEIGHT 64
+static struct value	vstack[STACK_HEIGHT];
+static int		stackh;
+
+static struct envs envs = TAILQ_HEAD_INITIALIZER(envs);
+
+static struct value v_false = {.type = V_NUM, .v = {.num = 0}};
+static struct value v_true  = {.type = V_NUM, .v = {.num = 1}};
+
+static uint8_t lasttag;
+
+static int debug;
+static int syntaxcheck;
+
+static const char	*filler;
+
+static inline void
+before_printing(void)
+{
+	if (filler != NULL) {
+		printf("%s", filler);
+		filler = NULL;
+	}
+}
+
+static inline void
+check_for_output(void)
+{
+	static char	buf[BUFSIZ];
+	struct pollfd	pfd;
+	ssize_t		r;
+
+	pfd.fd = child_out;
+	pfd.events = POLLIN;
+	if (poll(&pfd, 1, 0) == -1)
+		fatal("poll");
+
+	if (!(pfd.revents & POLLIN))
+		return;
+
+	for (;;) {
+		if ((r = read(child_out, buf, sizeof(buf))) == -1) {
+			if (errno == EAGAIN)
+				break;
+			fatal("read");
+		}
+		if (r == 0)
+			break;
+		before_printing();
+		fwrite(buf, 1, r, stdout);
+	}
+}
+
+static inline void
+peekn(int depth, struct value *v)
+{
+	if (depth > stackh)
+		errx(1, "can't peek the stack at %d: underflow",
+		    depth);
+	memcpy(v, &vstack[stackh - depth], sizeof(*v));
+
+#if DEBUG
+	printf("peeking(%d) ", depth); pp_val(v); printf("\n");
+#endif
+}
+
+static inline void
+popv(struct value *v)
+{
+	if (stackh == 0)
+		errx(1, "can't pop the stack: underflow");
+	memcpy(v, &vstack[--stackh], sizeof(*v));
+
+#if DEBUG
+	printf("popping "); pp_val(v); printf("\n");
+#endif
+}
+
+static inline void
+popvn(int n)
+{
+	struct value v;
+
+	while (n-- > 0)
+		popv(&v);
+}
+
+static inline void
+pushv(struct value *v)
+{
+	if (stackh == STACK_HEIGHT)
+		errx(1, "can't push the stack: overflow");
+
+#if DEBUG
+	printf("pushing "); pp_val(v); printf("\n");
+#endif
+
+	memcpy(&vstack[stackh++], v, sizeof(*v));
+}
+
+static inline void
+pushbool(int n)
+{
+	pushv(n ? &v_true : &v_false);
+}
+
+static inline void
+pushnum(int64_t n)
+{
+	struct value v;
+
+	v.type = V_NUM;
+	v.v.num = n;
+	pushv(&v);
+}
+
+static inline struct opstack *
+pushstack(struct opstacks *stack)
+{
+	struct opstack *ops;
+
+	ops = xcalloc(1, sizeof(*ops));
+	TAILQ_INSERT_HEAD(stack, ops, entry);
+	return ops;
+}
+
+static inline struct opstack *
+peek(struct opstacks *stack)
+{
+	if (TAILQ_EMPTY(stack))
+		errx(1, "%s: args underflow", __func__);
+
+	return TAILQ_FIRST(stack);
+}
+
+static inline struct op *
+finalize(struct opstacks *stack, int *argc)
+{
+	struct opstack	*ops;
+	struct op	*op;
+
+	if (TAILQ_EMPTY(stack))
+		errx(1, "%s: args underflow", __func__);
+
+	ops = peek(stack);
+	TAILQ_REMOVE(&args, ops, entry);
+	op = ops->base.next;
+
+	if (argc != NULL)
+		*argc = ops->counter;
+
+	free(ops);
+	return op;
+}
+
+static inline void
+push(struct opstacks *stack, struct op *op)
+{
+	struct opstack *ops;
+
+	ops = peek(stack);
+	if (ops->last == NULL) {
+		ops->base.next = op;
+		ops->last = op;
+	} else {
+		ops->last->next = op;
+		ops->last = op;
+	}
+
+	ops->counter++;
+}
+
+static inline void
+pushenv(void)
+{
+	struct env	*e;
+
+	e = xcalloc(1, sizeof(*e));
+	TAILQ_INSERT_HEAD(&envs, e, entry);
+}
+
+static inline struct env *
+currentenv(void)
+{
+	assert(!TAILQ_EMPTY(&envs));
+	return TAILQ_FIRST(&envs);
+}
+
+static void
+popenv(void)
+{
+	struct env	*e;
+	struct binding	*b, *tb;
+
+	e = currentenv();
+	TAILQ_REMOVE(&envs, e, entry);
+
+	TAILQ_FOREACH_SAFE(b, &e->bindings, entry, tb)
+		free(b);
+
+	free(e);
+}
+
+static inline int
+setvar(char *sym, struct op *op)
+{
+	struct binding	*b;
+	struct env	*e;
+	int		 ret, height;
+
+	height = stackh;
+	if ((ret = eval(op)) != EVAL_OK)
+		return ret;
+
+	if (stackh != height + 1) {
+		before_printing();
+		printf("trying to assign to `%s' a void value: ", sym);
+		pp_op(op);
+		printf("\n");
+		return EVAL_ERR;
+	}
+
+	b = xcalloc(1, sizeof(*b));
+	b->name = sym;
+	popv(&b->val);
+
+	e = TAILQ_FIRST(&envs);
+	TAILQ_INSERT_HEAD(&e->bindings, b, entry);
+
+	return EVAL_OK;
+}
+
+static inline void
+setvar_raw(char *sym, struct op *op)
+{
+	struct binding	*b;
+	struct env	*e;
+
+	b = xcalloc(1, sizeof(*b));
+	b->name = sym;
+	b->raw = op;
+
+	e = TAILQ_FIRST(&envs);
+	TAILQ_INSERT_HEAD(&e->bindings, b, entry);
+}
+
+static inline int
+getvar(const char *sym, struct value *v)
+{
+	struct env	*e;
+	struct binding	*b;
+
+	TAILQ_FOREACH(e, &envs, entry) {
+		TAILQ_FOREACH(b, &e->bindings, entry) {
+			if (!strcmp(sym, b->name)) {
+				memcpy(v, &b->val, sizeof(*v));
+				return EVAL_OK;
+			}
+		}
+	}
+
+	before_printing();
+	fprintf(stderr, "unbound variable %s\n", sym);
+	return EVAL_ERR;
+}
+
+static inline int
+getvar_raw(const char *sym, struct op **raw)
+{
+	struct env	*e;
+	struct binding	*b;
+
+	TAILQ_FOREACH(e, &envs, entry) {
+		TAILQ_FOREACH(b, &e->bindings, entry) {
+			if (!strcmp(sym, b->name)) {
+				*raw = b->raw;
+				return EVAL_OK;
+			}
+		}
+	}
+
+	return EVAL_ERR;
+}
+
+int
+global_set(char *sym, struct op *op)
+{
+	struct binding	*b;
+	struct env	*e;
+
+	/* TODO: check for duplicates */
+
+	if (op->type != OP_LITERAL &&
+	    (op->type == OP_CAST && op->v.cast.expr->type != OP_LITERAL))
+		return 0;
+
+	b = xcalloc(1, sizeof(*b));
+	b->name = sym;
+
+	/* it's only a cast on a literal! */
+	if (op->type == OP_CAST) {
+		if (eval(op) != EVAL_OK) {
+			free(b);
+			return 0;
+		}
+		popv(&b->val);
+	} else
+		memcpy(&b->val, &op->v.literal, sizeof(b->val));
+
+	e = TAILQ_LAST(&envs, envs);
+	TAILQ_INSERT_HEAD(&e->bindings, b, entry);
+
+	return 1;
+}
+
+struct op *
+newop(int type)
+{
+	struct op *op;
+
+	op = xcalloc(1, sizeof(*op));
+	op->type = type;
+
+	return op;
+}
+
+void
+free_op_rec(struct op *op)
+{
+	struct op *n;
+
+	while (op != NULL) {
+		n = op->next;
+		free_op(op);
+		op = n;
+	}
+}
+
+void
+free_op(struct op *op)
+{
+	if (op == NULL)
+		return;
+
+	switch (op->type) {
+	case OP_REST:
+	case OP_LITERAL:
+	case OP_VARGS:
+		break;
+	case OP_ASSIGN:
+		free(op->v.assign.name);
+		free_op_rec(op->v.assign.expr);
+		break;
+	case OP_ASSERT:
+		free_op_rec(op->v.assert);
+		break;
+	case OP_FUNCALL:
+		free_op_rec(op->v.funcall.argv);
+		break;
+	case OP_VAR:
+		free(op->v.var);
+		break;
+	case OP_CAST:
+		free_op_rec(op->v.cast.expr);
+		break;
+	case OP_CMP_EQ:
+	case OP_CMP_LEQ:
+		free_op_rec(op->v.bin_cmp.a);
+		free_op_rec(op->v.bin_cmp.b);
+		break;
+	case OP_FACCESS:
+		free_op_rec(op->v.faccess.expr);
+		free(op->v.faccess.field);
+		break;
+	case OP_SFAIL:
+		free(op->v.sfail.msg);
+		free_op_rec(op->v.sfail.expr);
+		break;
+	default:
+		/* unreachable */
+		abort();
+	}
+
+	free(op);
+}
+
+struct op *
+op_rest(void)
+{
+	return newop(OP_REST);
+}
+
+struct op *
+op_assign(char *sym, struct op *expr)
+{
+	struct op *op;
+
+	op = newop(OP_ASSIGN);
+	op->v.assign.name = sym;
+	op->v.assign.expr = expr;
+
+	return op;
+}
+
+struct op *
+op_assert(struct op *expr)
+{
+	struct op *op;
+
+	op = newop(OP_ASSERT);
+	op->v.assert = expr;
+
+	return op;
+}
+
+struct op *
+op_var(char *sym)
+{
+	struct op *op;
+
+	op = newop(OP_VAR);
+	op->v.var = sym;
+
+	return op;
+}
+
+struct op *
+op_lit_str(char *str)
+{
+	struct op *op;
+
+	op = newop(OP_LITERAL);
+	op->v.literal.type = V_STR;
+	op->v.literal.v.str = str;
+
+	return op;
+}
+
+struct op *
+op_lit_num(uint64_t n)
+{
+	struct op *op;
+
+	op = newop(OP_LITERAL);
+	op->v.literal.type = V_NUM;
+	op->v.literal.v.num = n;
+
+	return op;
+}
+
+struct op *
+op_cmp_eq(struct op *a, struct op *b)
+{
+	struct op *op;
+
+	op = newop(OP_CMP_EQ);
+	op->v.bin_cmp.a = a;
+	op->v.bin_cmp.b = b;
+
+	return op;
+}
+
+struct op *
+op_cmp_leq(struct op *a, struct op *b)
+{
+	struct op *op;
+
+	op = newop(OP_CMP_LEQ);
+	op->v.bin_cmp.a = a;
+	op->v.bin_cmp.b = b;
+
+	return op;
+}
+
+struct op *
+op_cast(struct op *expr, int totype)
+{
+	struct op *op;
+
+	op = newop(OP_CAST);
+	op->v.cast.expr = expr;
+	op->v.cast.totype = totype;
+
+	return op;
+}
+
+struct op *
+op_faccess(struct op *expr, char *field)
+{
+	struct op *op;
+
+	op = newop(OP_FACCESS);
+	op->v.faccess.expr = expr;
+	op->v.faccess.field = field;
+
+	return op;
+}
+
+struct op *
+op_sfail(struct op *expr, char *msg)
+{
+	struct op	*op;
+
+	op = newop(OP_SFAIL);
+	op->v.sfail.expr = expr;
+	op->v.sfail.msg = msg;
+
+	return op;
+}
+
+struct op *
+op_vargs(void)
+{
+	struct op	*op;
+
+	op = newop(OP_VARGS);
+
+	return op;
+}
+
+void
+ppf_val(FILE *f, struct value *val)
+{
+	size_t	i;
+
+	switch (val->type) {
+	case V_SYM:
+		fprintf(f, "%s", val->v.str);
+		break;
+	case V_STR:
+		fprintf(f, "\"%s\"", val->v.str);
+		break;
+	case V_NUM:
+		fprintf(f, "%"PRIi64, val->v.num);
+		break;
+	case V_U8:
+		fprintf(f, "%"PRIu8, val->v.u8);
+		break;
+	case V_U16:
+		fprintf(f, "%"PRIu16, val->v.u16);
+		break;
+	case V_U32:
+		fprintf(f, "%"PRIu32, val->v.u32);
+		break;
+	case V_MSG:
+		fprintf(f, "(");
+		for (i = 0; i < val->v.msg.len; ++i)
+			fprintf(f, "%x%s", val->v.msg.msg[i],
+			    i == val->v.msg.len-1 ? "" : " ");
+		fprintf(f, ")");
+		break;
+	case V_QIDVEC:
+		fprintf(f, "qids[n=%zu]", val->v.qidvec.len);
+		break;
+	default:
+		fprintf(f, "<unknown value>");
+		break;
+	}
+}
+
+void
+pp_val(struct value *val)
+{
+	ppf_val(stdout, val);
+}
+
+const char *
+val_type(struct value *v)
+{
+	switch (v->type) {
+	case V_SYM: return "symbol";
+	case V_STR: return "string";
+	case V_NUM: return "number";
+	case V_MSG: return "message";
+	case V_QID: return "qid";
+	case V_U8: return "u8";
+	case V_U16: return "u16";
+	case V_U32: return "u32";
+	default: return "unknown";
+	}
+}
+
+int
+val_trueish(struct value *a)
+{
+	if (val_isnum(a))
+		return val_tonum(a);
+	return 1;
+}
+
+int
+val_isnum(struct value *a)
+{
+	return a->type == V_NUM
+		|| a->type == V_U8
+		|| a->type == V_U16
+		|| a->type == V_U32;
+}
+
+int64_t
+val_tonum(struct value *a)
+{
+	switch (a->type) {
+	case V_NUM: return a->v.num;
+	case V_U8:  return a->v.u8;
+	case V_U16: return a->v.u16;
+	case V_U32: return a->v.u32;
+	default:
+		before_printing();
+		fprintf(stderr, "%s: given value is not a number\n", __func__);
+		abort();
+	}
+}
+
+int
+val_eq(struct value *a, struct value *b)
+{
+	if (val_isnum(a) && val_isnum(b))
+		return val_tonum(a) == val_tonum(b);
+
+	if (a->type != b->type)
+		return 0;
+
+	switch (a->type) {
+	case V_STR:
+	case V_SYM:
+		return !strcmp(a->v.str, b->v.str);
+	}
+
+	return 0;
+}
+
+int
+val_leq(struct value *a, struct value *b)
+{
+	if (val_isnum(a) && val_isnum(b))
+		return val_tonum(a) <= val_tonum(b);
+	return 0;
+}
+
+static inline const char *
+pp_totype(int totype)
+{
+	/*
+	 * Not all of these are valid cast type thought, including
+	 * every possibility only to aid debugging.
+	 */
+	switch (totype) {
+	case V_STR: return "str";
+	case V_SYM: return "sym";
+	case V_NUM: return "num";
+	case V_QID: return "qid";
+	case V_U8:  return "u8";
+	case V_U16: return "u16";
+	case V_U32: return "u32";
+	default:    return "unknown";
+	}
+}
+
+int
+val_cast(struct value *a, int totype)
+{
+	int64_t v;
+
+#define NUMCAST(val, t, c, totype, max) do {				\
+		if (val > max) {					\
+			before_printing();				\
+			fprintf(stderr, "can't cast %"PRIu64		\
+			    " to %s\n", val, pp_totype(totype));	\
+			return EVAL_ERR;				\
+		}							\
+		a->type = totype;					\
+		a->v.t = (c)val;					\
+		return EVAL_OK;						\
+	} while (0)
+
+	if (a->type == totype)
+		return EVAL_OK;
+
+	if (!val_isnum(a)) {
+		before_printing();
+		fprintf(stderr, "can't cast ");
+		ppf_val(stderr, a);
+		fprintf(stderr, " to type %s\n", pp_totype(totype));
+		return EVAL_ERR;
+	}
+
+	v = a->v.num;
+	switch (totype) {
+	case V_U8:  NUMCAST(v, u8,  uint8_t,  totype, UINT8_MAX);
+	case V_U16: NUMCAST(v, u16, uint16_t, totype, UINT16_MAX);
+	case V_U32: NUMCAST(v, u32, uint32_t, totype, UINT32_MAX);
+	default:
+		before_printing();
+		fprintf(stderr, "can't cast %"PRIu64" to %s\n",
+		    v, pp_totype(totype));
+		return EVAL_ERR;
+	}
+
+#undef NUMCAST
+}
+
+int
+val_faccess(struct value *a, const char *field, struct value *ret)
+{
+	uint8_t		 mtype;
+	uint16_t	 len;
+	const char	*errstr;
+
+#define MSGTYPE(m) *(m.msg + 4)	/* skip the length */
+
+	switch (a->type) {
+	case V_QID:
+		/* TODO: add path.  needs uint64_t values thought! */
+		if (!strcmp(field, "vers")) {
+			ret->type = V_U32;
+			memcpy(&ret->v.u32, a->v.qid+1, 4);
+			return EVAL_OK;
+		} else if (!strcmp(field, "type")) {
+			ret->type = V_U8;
+			ret->v.u8 = *a->v.qid;
+			return EVAL_OK;
+		}
+		break;
+
+	case V_MSG:
+		mtype = MSGTYPE(a->v.msg);
+		if (!strcmp(field, "type")) {
+			ret->type = V_U8;
+			ret->v.u8 = MSGTYPE(a->v.msg);
+			return EVAL_OK;
+		} else if (!strcmp(field, "tag")) {
+			ret->type = V_U16;
+                        memcpy(&ret->v.u16, &a->v.msg.msg[5], 2);
+			ret->v.u16 = le16toh(ret->v.u16);
+			return EVAL_OK;
+		} else if (!strcmp(field, "msize") && mtype == Rversion) {
+			ret->type = V_U32;
+			memcpy(&ret->v.u32, &a->v.msg.msg[7], 4);
+			ret->v.u32 = le32toh(ret->v.u32);
+			return EVAL_OK;
+		} else if (!strcmp(field, "qid") && mtype == Rattach) {
+			ret->type = V_QID;
+			memcpy(&ret->v.qid, &a->v.msg.msg[7], QIDSIZE);
+			return EVAL_OK;
+		} else if (!strcmp(field, "nwqid") && mtype == Rwalk) {
+			ret->type = V_U16;
+			memcpy(&ret->v.u16, &a->v.msg.msg[7], 2);
+			ret->v.u16 = le16toh(ret->v.u16);
+			return EVAL_OK;
+		} else if (!strcmp(field, "wqid") && mtype == Rwalk) {
+			ret->type = V_QIDVEC;
+			ret->v.qidvec.start = &a->v.msg.msg[9];
+			memcpy(&len, &a->v.msg.msg[7], 2);
+			len = le16toh(len);
+			ret->v.qidvec.len = len;
+			return EVAL_OK;
+		}
+		break;
+
+	case V_QIDVEC:
+		len = strtonum(field, 0, MAXWELEM, &errstr);
+		if (errstr != NULL) {
+			before_printing();
+			printf("can't access qid #%s: %s\n", field, errstr);
+			return EVAL_ERR;
+		}
+
+		if (len >= a->v.qidvec.len) {
+			before_printing();
+			printf("can't access qid #%d: out-of-bound "
+			    "(max %zu)\n", len, a->v.qidvec.len);
+			return EVAL_ERR;
+		}
+
+		ret->type = V_QID;
+		memcpy(&ret->v.qid, a->v.qidvec.start + len * QIDSIZE,
+		    QIDSIZE);
+
+                return EVAL_OK;
+
+	default:
+		break;
+	}
+
+	before_printing();
+	printf("can't access field `%s' on type %s (", field, val_type(a));
+	pp_val(a);
+	printf(")\n");
+	return EVAL_ERR;
+
+#undef MSGTYPE
+}
+
+void
+pp_op(struct op *op)
+{
+	struct op	*aux;
+
+	switch (op->type) {
+	case OP_REST:
+		printf("...");
+		break;
+	case OP_ASSIGN:
+		printf("%s = ", op->v.assign.name);
+                pp_op(op->v.assign.expr);
+		break;
+	case OP_ASSERT:
+		printf("assert ");
+		pp_op(op->v.assert);
+		break;
+	case OP_FUNCALL:
+		printf("funcall %s(", op->v.funcall.proc->name);
+		for (aux = op->v.funcall.argv; aux != NULL; aux = aux->next) {
+			pp_op(aux);
+			if (aux->next != NULL)
+				printf(", ");
+		}
+		printf(")");
+		break;
+	case OP_LITERAL:
+                pp_val(&op->v.literal);
+		break;
+	case OP_VAR:
+		printf("%s", op->v.var);
+		break;
+	case OP_CAST:
+		pp_op(op->v.cast.expr);
+		printf(":");
+		switch (op->v.cast.totype) {
+		case V_U8: printf("u8"); break;
+		case V_U16: printf("u16"); break;
+		case V_U32: printf("u32"); break;
+		case V_STR: printf("str"); break;
+		default: printf("???"); break;
+		}
+		break;
+	case OP_CMP_EQ:
+		pp_op(op->v.bin_cmp.a);
+		printf(" == ");
+		pp_op(op->v.bin_cmp.b);
+		break;
+	case OP_CMP_LEQ:
+		pp_op(op->v.bin_cmp.a);
+		printf(" <= ");
+		pp_op(op->v.bin_cmp.b);
+		break;
+	case OP_FACCESS:
+		pp_op(op->v.faccess.expr);
+		printf(".%s", op->v.faccess.field);
+		break;
+	case OP_SFAIL:
+		printf("should-fail ");
+		pp_op(op->v.sfail.expr);
+		if (op->v.sfail.msg != NULL)
+			printf(": \"%s\"", op->v.sfail.msg);
+		break;
+	case OP_VARGS:
+		printf("vargs");
+		break;
+	default:
+		printf(" ???[%d] ", op->type);
+	}
+}
+
+void
+pp_block(struct op *op)
+{
+        while (op != NULL) {
+		printf("> ");
+		pp_op(op);
+		printf("\n");
+
+		op = op->next;
+	}
+}
+
+int
+eval(struct op *op)
+{
+	struct value	 a, b;
+	struct proc	*proc;
+	struct op	*t, *tnext;
+	int		 i, ret;
+
+#if DEBUG
+	pp_op(op);
+	printf("\n");
+#endif
+
+	switch (op->type) {
+	case OP_REST:
+		/*
+		 * Try to load the rest argument.  Note that it can be
+		 * empty!
+		 */
+                if ((ret = getvar_raw("...", &t)) == EVAL_OK)
+			if ((ret = eval(t)) != EVAL_OK)
+				return ret;
+		break;
+
+	case OP_ASSIGN:
+		ret = setvar(op->v.assign.name, op->v.assign.expr);
+		if (ret != EVAL_OK)
+			return ret;
+		break;
+
+	case OP_ASSERT:
+		if ((ret = eval(op->v.assert)) != EVAL_OK)
+			return ret;
+                popv(&a);
+                if (!val_trueish(&a)) {
+			before_printing();
+			printf("assertion failed: ");
+			pp_op(op->v.assert);
+			printf("\n");
+			return EVAL_ERR;
+		}
+		break;
+
+	case OP_FUNCALL:
+		/* assume airity matches */
+
+		proc = op->v.funcall.proc;
+		if (proc->nativefn != NULL) {
+			/*
+			 * Push arguments on the stack for builtin
+			 * functions.  Counting the height of the
+			 * stack is done to compute the correct number
+			 * in the vararg case.  argc only counts the
+			 * "syntactical" arguments, i.e. foo(x, ...)
+			 * has argc == 2, but at runtime argc may be
+			 * 1, 2 or a greater number!
+			 */
+
+			i = stackh;
+			t = op->v.funcall.argv;
+			if (t != NULL && (ret = eval(t)) != EVAL_OK)
+				return ret;
+			i = stackh - i;
+
+			assert(i >= 0);
+
+			if ((ret = proc->nativefn(i))
+			    != EVAL_OK)
+				return ret;
+		} else {
+			if (proc->body == NULL) {
+				before_printing();
+				printf("warn: calling the empty proc `%s'\n",
+				    proc->name);
+				break;
+			}
+
+			pushenv();
+
+			for (t = op->v.funcall.argv, i = 0;
+			     t != NULL;
+			     t = t->next, i++) {
+				/*
+				 * Push a pseudo variable `...' (and
+				 * don't evaluate it) in the vararg
+				 * case.  A special case is when the
+				 * variable is itself `...'.
+				 */
+				if (proc->vararg && i == proc->minargs) {
+					if (t->type != OP_REST)
+						setvar_raw(xstrdup("..."), t);
+					break;
+				}
+
+				/*
+				 * The arguments are a linked list of
+				 * ops.  Setvar will call eval that
+				 * will evaluate *all* the arguments.
+				 * The dance here that sets next to
+				 * NULL and then restores it is to
+				 * avoid this behaviour.
+				 */
+				tnext = t->next;
+				t->next = NULL;
+				ret = setvar(proc->args[i], t);
+				t->next = tnext;
+
+				if (ret != EVAL_OK)
+					return ret;
+			}
+
+			if ((ret = eval(proc->body)) != EVAL_OK)
+				return ret;
+
+			popenv();
+		}
+
+		break;
+
+	case OP_LITERAL:
+		pushv(&op->v.literal);
+		break;
+
+	case OP_VAR:
+                if ((ret = getvar(op->v.var, &a)) != EVAL_OK)
+			return ret;
+		pushv(&a);
+		break;
+
+	case OP_CAST:
+		if ((ret = eval(op->v.cast.expr)) != EVAL_OK)
+			return ret;
+		popv(&a);
+		if ((ret = val_cast(&a, op->v.cast.totype)) != EVAL_OK)
+			return ret;
+		pushv(&a);
+		break;
+
+	case OP_CMP_EQ:
+		if ((ret = eval(op->v.bin_cmp.a)) != EVAL_OK)
+			return ret;
+		if ((ret = eval(op->v.bin_cmp.b)) != EVAL_OK)
+			return ret;
+
+		popv(&b);
+		popv(&a);
+		pushbool(val_eq(&a, &b));
+		break;
+
+	case OP_CMP_LEQ:
+		if ((ret = eval(op->v.bin_cmp.a)) != EVAL_OK)
+			return ret;
+		if ((ret = eval(op->v.bin_cmp.b)) != EVAL_OK)
+			return ret;
+
+		popv(&b);
+		popv(&a);
+		pushbool(val_leq(&a, &b));
+		break;
+
+	case OP_FACCESS:
+		if ((ret = eval(op->v.faccess.expr)) != EVAL_OK)
+			return ret;
+		popv(&a);
+		if ((ret = val_faccess(&a, op->v.faccess.field, &b))
+		    != EVAL_OK)
+			return ret;
+		pushv(&b);
+		break;
+
+	case OP_SFAIL:
+		if ((ret = eval(op->v.sfail.expr)) == EVAL_OK) {
+			before_printing();
+			printf("expecting failure");
+			if (op->v.sfail.msg != NULL)
+				printf(" \"%s\"", op->v.sfail.msg);
+			printf("\n");
+			printf("expression: ");
+			pp_op(op->v.sfail.expr);
+			printf("\n");
+			return EVAL_ERR;
+		}
+		if (ret == EVAL_SKIP)
+			return ret;
+		break;
+
+	case OP_VARGS:
+		if ((ret = getvar_raw("...", &t)) == EVAL_OK) {
+                        for (i = 0; t != NULL; t = t->next)
+				i++;
+			pushnum(i);
+		} else
+			pushnum(0);
+		break;
+
+	default:
+		before_printing();
+		fprintf(stderr, "invalid op, aborting.\n");
+		abort();
+	}
+
+	if (op->next)
+		return eval(op->next);
+	return EVAL_OK;
+}
+
+void
+prepare_funcall(void)
+{
+	pushstack(&args);
+}
+
+void
+push_arg(struct op *op)
+{
+	push(&args, op);
+}
+
+struct op *
+op_funcall(struct proc *proc)
+{
+	struct op	*op, *argv;
+	int		 argc;
+
+	argv = finalize(&args, &argc);
+
+	op = newop(OP_FUNCALL);
+	op->v.funcall.proc = proc;
+	op->v.funcall.argv = argv;
+	op->v.funcall.argc = argc;
+
+	return op;
+}
+
+void
+add_builtin_proc(const char *name, int (*fn)(int), int argc, int vararg)
+{
+	struct proc *proc;
+
+	proc = xcalloc(1, sizeof(*proc));
+	proc->name = xstrdup(name);
+	proc->nativefn = fn;
+	proc->minargs = argc;
+	proc->vararg = vararg;
+
+	TAILQ_INSERT_HEAD(&procs, proc, entry);
+}
+
+void
+prepare_proc(void)
+{
+	pushstack(&args);
+}
+
+int
+proc_setup_body(void)
+{
+	struct opstack	*argv;
+	struct op	*op;
+	int		 i;
+
+	argv = peek(&args);
+	for (i = 0, op = argv->base.next; op != NULL; i++) {
+		/*
+		 * TODO: should free the whole list on error but..,
+		 * we're gonna exit real soon(tm)!
+		 */
+		if (op->type != OP_VAR && op->type != OP_REST)
+			return 0;
+
+		op = op->next;
+	}
+
+	assert(i == argv->counter);
+	pushstack(&blocks);
+	return 1;
+}
+
+void
+proc_done(char *name)
+{
+	struct proc	*proc;
+	struct op	*op, *next, *argv, *body;
+	int		 i, argc;
+
+	argv = finalize(&args, &argc);
+	body = finalize(&blocks, NULL);
+
+	proc = xcalloc(1, sizeof(*proc));
+	proc->name = name;
+	proc->minargs = argc;
+
+        for (i = 0, op = argv; op != NULL; ++i) {
+		if (op->type == OP_REST) {
+			proc->vararg = 1;
+			proc->minargs = i;
+			break;
+		}
+
+		proc->args[i] = xstrdup(op->v.var);
+
+		next = op->next;
+		free_op(op);
+		op = next;
+	}
+	assert(i == argc || (proc->vararg && i == proc->minargs));
+
+	proc->body = body;
+
+	TAILQ_INSERT_HEAD(&procs, proc, entry);
+}
+
+void
+block_push(struct op *op)
+{
+	push(&blocks, op);
+}
+
+struct proc *
+proc_by_name(const char *name)
+{
+	struct proc *p;
+
+	TAILQ_FOREACH(p, &procs, entry) {
+		if (!strcmp(p->name, name))
+			return p;
+	}
+
+	return NULL;
+}
+
+void
+prepare_test(void)
+{
+	pushstack(&blocks);
+}
+
+void
+test_done(int shouldfail, char *name, char *dir)
+{
+	struct test	*test;
+
+	test = xcalloc(1, sizeof(*test));
+	test->shouldfail = shouldfail;
+	test->name = name;
+	test->dir = dir;
+	test->body = finalize(&blocks, NULL);
+
+	if (TAILQ_EMPTY(&tests))
+		TAILQ_INSERT_HEAD(&tests, test, entry);
+	else
+		TAILQ_INSERT_TAIL(&tests, test, entry);
+
+	ntests++;
+}
+
+static int
+builtin_print(int argc)
+{
+	struct value	v;
+	int		i;
+
+	before_printing();
+
+	for (i = argc; i > 0; --i) {
+		peekn(i, &v);
+		if (v.type == V_STR)
+			printf("%s", v.v.str);
+		else
+			pp_val(&v);
+		printf(" ");
+	}
+
+	printf("\n");
+
+	popvn(argc);
+
+	return EVAL_OK;
+}
+
+static int
+builtin_debug(int argc)
+{
+	if (debug)
+		return builtin_print(argc);
+
+	popvn(argc);
+	return EVAL_OK;
+}
+
+static int
+builtin_skip(int argc)
+{
+	return EVAL_SKIP;
+}
+
+static int
+builtin_iota(int argc)
+{
+	struct value v;
+
+	v.type = V_U16;
+	if ((v.v.u16 = ++lasttag) == 255)
+		v.v.u16 = ++lasttag;
+
+	pushv(&v);
+	return EVAL_OK;
+}
+
+static int
+builtin_send(int argc)
+{
+	struct ibuf	*buf;
+	struct value	 v;
+	uint32_t	 len;
+	uint16_t	 slen;
+	int		 i;
+
+	check_for_output();
+
+	/*
+	 * Compute the length of the packet.  4 is for the initial
+	 * length field
+	 */
+	len = 4;
+
+	for (i = argc; i > 0; --i) {
+		peekn(i, &v);
+		switch (v.type) {
+		case V_STR:
+			len += 2; /* count */
+			len += strlen(v.v.str);
+			break;
+
+		case V_U8:
+			len += 1;
+			break;
+
+		case V_U16:
+			len += 2;
+			break;
+
+		case V_U32:
+			len += 4;
+			break;
+
+		default:
+			before_printing();
+			printf("%s: can't serialize ", __func__);
+			pp_val(&v);
+			printf("\n");
+			return EVAL_ERR;
+		}
+	}
+
+	if (len > UINT16_MAX) {
+		before_printing();
+		printf("%s: message size too long: got %d when max is %d\n",
+		    __func__, len, UINT16_MAX);
+		return EVAL_ERR;
+	}
+
+	if ((buf = imsg_create(&ibuf, IMSG_BUF, 0, 0, len)) == NULL)
+		fatal("imsg_create(%d)", len);
+
+	len = htole32(len);
+	imsg_add(buf, &len, sizeof(len));
+
+	for (i = argc; i > 0; --i) {
+		peekn(i, &v);
+		switch (v.type) {
+		case V_STR:
+			slen = strlen(v.v.str);
+			slen = htole16(slen);
+			imsg_add(buf, &slen, sizeof(slen));
+			imsg_add(buf, v.v.str, strlen(v.v.str));
+			break;
+
+		case V_U8:
+			imsg_add(buf, &v.v.u8, 1);
+			break;
+
+		case V_U16:
+			v.v.u16 = htole16(v.v.u16);
+			imsg_add(buf, &v.v.u16, 2);
+			break;
+
+		case V_U32:
+			v.v.u32 = htole32(v.v.u32);
+			imsg_add(buf, &v.v.u32, 4);
+			break;
+		}
+	}
+
+	imsg_close(&ibuf, buf);
+
+	if (imsg_flush(&ibuf) == -1) {
+		i = errno;
+		before_printing();
+		printf("%s: imsg_flush failed: %s\n", __func__, strerror(i));
+		return EVAL_ERR;
+	}
+
+	check_for_output();
+	return EVAL_OK;
+}
+
+static int
+builtin_recv(int argc)
+{
+	struct pollfd	pfd;
+        struct value	v;
+	struct imsg	imsg;
+	ssize_t		n, datalen;
+	int		serrno;
+
+	if (lastmsg != NULL) {
+		free(lastmsg);
+		lastmsg = NULL;
+	}
+
+	pfd.fd = ibuf.fd;
+	pfd.events = POLLIN;
+	if (poll(&pfd, 1, INFTIM) == -1) {
+		serrno = errno;
+		before_printing();
+		printf("%s: poll failed: %s\n", __func__, strerror(serrno));
+		return EVAL_ERR;
+	}
+
+again:
+	if ((n = imsg_read(&ibuf)) == -1) {
+		if (errno == EAGAIN)
+			goto again;
+		fatal("imsg_read");
+	}
+	if (n == 0) {
+disconnect:
+		before_printing();
+		printf("child disconnected\n");
+		return EVAL_ERR;
+	}
+
+nextmessage:
+	check_for_output();
+
+	/* read only one message */
+	if ((n = imsg_get(&ibuf, &imsg)) == -1)
+		fatal("imsg_get");
+	if (n == 0)
+		goto disconnect;
+
+	datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
+	switch (imsg.hdr.type) {
+	case IMSG_BUF:
+		v.type = V_MSG;
+		if ((v.v.msg.msg = malloc(datalen)) == NULL)
+			fatal("malloc");
+		memcpy(v.v.msg.msg, imsg.data, datalen);
+		v.v.msg.len = datalen;
+		pushv(&v);
+		imsg_free(&imsg);
+                return EVAL_OK;
+
+	case IMSG_CLOSE:
+		before_printing();
+		printf("subprocess closed the connection\n");
+		imsg_free(&imsg);
+		return EVAL_ERR;
+
+	case IMSG_MSIZE:
+		imsg_free(&imsg);
+		goto nextmessage;
+
+	default:
+		before_printing();
+		printf("got unknown message from subprocess: %d\n",
+		    imsg.hdr.type);
+		imsg_free(&imsg);
+		return EVAL_ERR;
+	}
+}
+
+static pid_t
+spawn_client_proc(void)
+{
+	const char	*argv[4];
+	int		 p[2], out[2], argc = 0;
+	pid_t		 pid;
+
+	if (child_out != -1)
+		close(child_out);
+
+	if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
+	    PF_UNSPEC, p) == -1)
+		fatal("socketpair");
+
+	if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
+	    PF_UNSPEC, out) == -1)
+		fatal("socketpair");
+
+	switch (pid = fork()) {
+	case -1:
+		fatal("cannot fork");
+	case 0:
+		break;
+	default:
+		close(p[1]);
+		close(out[1]);
+		child_out = out[0];
+		if (ibuf_inuse) {
+			msgbuf_clear(&ibuf.w);
+			close(ibuf.fd);
+		}
+		imsg_init(&ibuf, p[0]);
+		ibuf_inuse = 1;
+		return pid;
+	}
+
+	close(p[0]);
+	close(out[0]);
+
+	if (dup2(out[1], 1) == -1 ||
+	    dup2(out[1], 2) == -1)
+		fatal("dup2");
+
+	if (p[1] != 3) {
+		if (dup2(p[1], 3) == -1)
+			fatal("cannot setup imsg fd");
+	} else if (fcntl(F_SETFD, 0) == -1)
+		fatal("cannot setup imsg fd");
+
+	argv[argc++] = argv0;
+	argv[argc++] = "-Tc";
+
+#if DEBUG
+	argv[argc++] = "-v";
+#endif
+
+	argv[argc++] = NULL;
+
+	execvp(argv0, (char *const *)argv);
+	fatal("execvp");
+}
+
+static void
+prepare_child_for_test(struct test *t)
+{
+	struct passwd	*pw;
+	struct stat	 sb;
+
+	if (stat(t->dir, &sb) == -1)
+		fatal("stat(\"%s\")", t->dir);
+
+	if ((pw = getpwuid(sb.st_uid)) == NULL)
+		fatal("getpwuid(%d)", sb.st_uid);
+
+	imsg_compose(&ibuf, IMSG_AUTH, 0, 0, -1,
+	    pw->pw_name, strlen(pw->pw_name)+1);
+	imsg_compose(&ibuf, IMSG_AUTH_DIR, 0, 0, -1,
+	    t->dir, strlen(t->dir)+1);
+
+	if (imsg_flush(&ibuf) == -1)
+		fatal("imsg_flush");
+}
+
+static int
+run_test(struct test *t)
+{
+	pid_t	pid;
+	int	ret;
+
+#if DEBUG
+	before_printing();
+	puts("=====================");
+	pp_block(t->body);
+	puts("=====================");
+#endif
+
+        if (stackh != 0)
+		popvn(stackh);
+
+	if (t->body == NULL) {
+		before_printing();
+		printf("no instructions, skipping...\n");
+		return EVAL_SKIP;
+	}
+
+	pid = spawn_client_proc();
+        prepare_child_for_test(t);
+	ret = eval(t->body);
+
+	imsg_compose(&ibuf, IMSG_CONN_GONE, 0, 0, -1, NULL, 0);
+	imsg_flush(&ibuf);
+
+	while (waitpid(pid, NULL, 0) != pid)
+		; /* nop */
+
+	check_for_output();
+
+	if (t->shouldfail) {
+		if (ret == EVAL_OK) {
+			before_printing();
+			printf("test was expected to fail\n");
+			return EVAL_ERR;
+		} else if (ret == EVAL_ERR)
+			return EVAL_OK;
+	}
+
+	return ret;
+}
+
+int
+main(int argc, char **argv)
+{
+	struct test	*t;
+	int		 ch, i, r, passed = 0, failed = 0, skipped = 0;
+	int		 runclient = 0;
+	const char	*pat = NULL;
+	regex_t		 reg;
+
+	assert(argv0 = argv[0]);
+
+	signal(SIGPIPE, SIG_IGN);
+
+	log_init(1, LOG_DAEMON);
+	log_setverbose(1);
+
+	/* prepare the global env */
+	pushenv();
+
+	add_builtin_proc("print", builtin_print, 1, 1);
+	add_builtin_proc("debug", builtin_debug, 1, 1);
+	add_builtin_proc("skip", builtin_skip, 0, 0);
+	add_builtin_proc("iota", builtin_iota, 0, 0);
+	add_builtin_proc("send", builtin_send, 2, 1);
+	add_builtin_proc("recv", builtin_recv, 0, 0);
+
+	while ((ch = getopt(argc, argv, "nT:vx:")) != -1) {
+		switch (ch) {
+		case 'n':
+			syntaxcheck = 1;
+			break;
+		case 'T':
+			assert(*optarg == 'c');
+                        runclient = 1;
+			break;
+		case 'v':
+			debug = 1;
+			break;
+		case 'x':
+			pat = optarg;
+			break;
+		default:
+			fprintf(stderr, "Usage: %s [-nv] [files...]\n",
+			    *argv);
+			exit(1);
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (runclient)
+		client(1, debug);
+
+	if (pat == NULL)
+		pat = ".*";
+
+	if (regcomp(&reg, pat, REG_BASIC | REG_ICASE | REG_NOSUB) != 0)
+		fatalx("invalid regexp: %s", pat);
+
+	for (i = 0; i < argc; ++i)
+		loadfile(argv[i]);
+
+	if (syntaxcheck) {
+		fprintf(stderr, "files OK\n");
+		return 0;
+	}
+
+	/* Check for root privileges. */
+        if (geteuid())
+                fatalx("need root privileges");
+
+	i = 0;
+	TAILQ_FOREACH(t, &tests, entry) {
+		if (regexec(&reg, t->name, 0, NULL, 0) != 0)
+			continue;
+
+		printf("===> [%d/%d] running test \"%s\"... ", i+1, ntests,
+		    t->name);
+		fflush(stdout);
+
+		filler = "\n";
+		r = run_test(t);
+		if (filler == NULL)
+			printf("=> test ");
+
+		switch (r) {
+		case EVAL_OK:
+			printf("passed\n");
+			passed++;
+			break;
+		case EVAL_ERR:
+			failed++;
+			printf("failed\n");
+			break;
+		case EVAL_SKIP:
+			printf("skipped\n");
+			skipped++;
+			break;
+		}
+
+		if (filler == NULL)
+			printf("\n");
+		i++;
+	}
+
+	printf("\n");
+	printf("%d/%d passed (%d skipped and %d failed)\n",
+	    passed, i, skipped, failed);
+
+	popenv();
+	free(lastmsg);
+	regfree(&reg);
+
+	return failed != 0;
+}
blob - /dev/null
blob + 1e5584e6ac031d9590e267d34b6c282095a7a1be (mode 644)
--- /dev/null
+++ ninepscript/script.h
@@ -0,0 +1,217 @@
+/*
+ * 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.
+ */
+
+#ifndef SCRIPT_H
+#define SCRIPT_H
+
+enum {
+	/* literals */
+	V_SYM,
+	V_STR,
+	V_NUM,
+
+	/* foreign */
+	V_MSG,
+	V_QIDVEC,
+	V_QID,
+
+	/* casted */
+	V_U8,
+	V_U16,
+	V_U32,
+};
+
+struct value {
+	int type;
+	union {
+		char		*str;
+		int64_t		 num;
+		uint8_t		 u8;
+		uint16_t	 u16;
+		uint32_t	 u32;
+		struct {
+			uint8_t	*msg;
+			size_t	 len;
+		} msg;
+		struct {
+			uint8_t	*start;
+			size_t	 len;
+		} qidvec;
+		uint8_t		 qid[QIDSIZE];
+	} v;
+};
+
+enum {
+	OP_REST,
+	OP_ASSIGN,
+	OP_ASSERT,
+	OP_FUNCALL,
+	OP_LITERAL,
+	OP_VAR,
+	OP_CAST,
+	OP_CMP_EQ,
+	OP_CMP_LEQ,
+	OP_FACCESS,
+	OP_SFAIL,
+	OP_VARGS,
+};
+
+struct proc;
+
+struct op {
+	struct op	*next;
+	int		 type;
+	union {
+		struct {
+			char		*name;
+			struct op	*expr;
+		} assign;
+		struct op		*assert;
+		struct {
+			struct proc	*proc;
+			struct op	*argv;
+			int		 argc;
+		} funcall;
+		struct value literal;
+		char *var;
+		struct {
+			struct op	*expr;
+			int		 totype;
+		} cast;
+		struct {
+			struct op	*a;
+			struct op	*b;
+		} bin_cmp;
+		struct {
+			struct op	*expr;
+			char		*field;
+		} faccess;
+		struct {
+			char		*msg;
+			struct op	*expr;
+		} sfail;
+	} v;
+};
+
+TAILQ_HEAD(bindings, binding);
+struct binding {
+	TAILQ_ENTRY(binding)	 entry;
+	char			*name;
+	struct value		 val;
+
+	/*
+	 * Hack to support varargs.  We set a special variable named
+	 * "..." that contains the list of ops that will evaluate to
+	 * the arguments.
+	 */
+	struct op		*raw;
+};
+
+TAILQ_HEAD(envs, env);
+struct env {
+	TAILQ_ENTRY(env)	 entry;
+	struct bindings		 bindings;
+};
+
+TAILQ_HEAD(opstacks, opstack);
+struct opstack {
+	TAILQ_ENTRY(opstack)	 entry;
+	struct op		 base;
+	struct op		*last;
+	int			 counter;
+};
+
+TAILQ_HEAD(procs, proc);
+struct proc {
+	TAILQ_ENTRY(proc)	 entry;
+	char			*name;
+	int			 minargs;
+	int			 vararg;
+	char			*args[MAXWELEM];
+	struct op		*body;
+	int			(*nativefn)(int);
+};
+
+TAILQ_HEAD(tests, test);
+struct test {
+	TAILQ_ENTRY(test)	 entry;
+	int			 shouldfail;
+	char			*name;
+	char			*dir;
+	struct op		*body;
+};
+
+enum {
+	EVAL_OK,
+	EVAL_ERR,
+	EVAL_SKIP,
+};
+
+int		 global_set(char *, struct op *);
+
+struct op	*newop(int);
+void		 free_op_rec(struct op *);
+void		 free_op(struct op *);
+struct op	*op_rest(void);
+struct op	*op_assign(char *, struct op *);
+struct op	*op_assert(struct op *);
+struct op	*op_var(char *);
+struct op	*op_lit_str(char *);
+struct op	*op_lit_num(uint64_t);
+struct op	*op_cmp_eq(struct op *, struct op *);
+struct op	*op_cmp_leq(struct op *, struct op *);
+struct op	*op_cast(struct op *, int);
+struct op	*op_faccess(struct op *, char *);
+struct op	*op_sfail(struct op *, char *);
+struct op	*op_vargs(void);
+
+void		 ppf_val(FILE *, struct value *);
+void		 pp_val(struct value *);
+void		 pp_val(struct value *);
+const char	*val_type(struct value *);
+int		 val_trueish(struct value *);
+int		 val_isnum(struct value *);
+int64_t		 val_tonum(struct value *);
+int		 val_eq(struct value *, struct value *);
+int		 val_leq(struct value *, struct value *);
+int		 val_cast(struct value *, int);
+int		 val_faccess(struct value *, const char *, struct value *);
+void		 pp_op(struct op *);
+void		 pp_block(struct op *);
+int		 eval(struct op *);
+
+/* funcall */
+void		 prepare_funcall(void);
+void		 push_arg(struct op *);
+struct op	*op_funcall(struct proc *);
+
+/* proc */
+void		 add_builtin_proc(const char *name, int (*)(int), int, int);
+void		 prepare_proc(void);
+/* push_arg works on procs too */
+int		 proc_setup_body(void);
+void		 proc_done(char *name);
+void		 block_push(struct op *);
+struct proc	*proc_by_name(const char *);
+
+/* testing */
+void		 prepare_test(void);
+void		 test_done(int, char *, char *);
+
+/* np.y */
+void		 loadfile(const char *);
+
+#endif
blob - 16e2d92655d1686171735ca8ef78d3ddb69907f9 (mode 644)
blob + /dev/null
--- control.c
+++ /dev/null
@@ -1,291 +0,0 @@
-/*
- * 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 "compat.h"
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/socket.h>
-#include <sys/uio.h>
-#include <sys/un.h>
-
-#include <netinet/in.h>
-#include <net/if.h>
-
-#include <errno.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include "control.h"
-#include "kamid.h"
-#include "listener.h"
-#include "log.h"
-#include "utils.h"
-
-#define	CONTROL_BACKLOG	5
-
-struct {
-	struct event	ev;
-	struct event	evt;
-	int		fd;
-} control_state = {.fd = -1};
-
-struct ctl_conn {
-	TAILQ_ENTRY(ctl_conn)	entry;
-	struct imsgev		iev;
-};
-
-TAILQ_HEAD(ctl_conns, ctl_conn)	ctl_conns = TAILQ_HEAD_INITIALIZER(ctl_conns);
-
-struct ctl_conn	*control_connbyfd(int);
-struct ctl_conn	*control_connbypid(pid_t);
-void		 control_close(int);
-
-int
-control_init(const char *path)
-{
-	struct sockaddr_un	 sun;
-	int			 fd;
-	mode_t			 old_umask;
-
-	if ((fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
-	    0)) == -1) {
-		log_warn("%s: socket", __func__);
-		return (-1);
-	}
-
-	memset(&sun, 0, sizeof(sun));
-	sun.sun_family = AF_UNIX;
-	strlcpy(sun.sun_path, path, sizeof(sun.sun_path));
-
-	if (unlink(path) == -1)
-		if (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", __func__, path);
-		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", __func__);
-		close(fd);
-		(void)unlink(path);
-		return (-1);
-	}
-
-	return (fd);
-}
-
-int
-control_listen(int fd)
-{
-	if (control_state.fd != -1)
-		fatalx("%s: received unexpected controlsock", __func__);
-
-	control_state.fd = fd;
-	if (listen(control_state.fd, CONTROL_BACKLOG) == -1) {
-		log_warn("%s: listen", __func__);
-		return (-1);
-	}
-
-	event_set(&control_state.ev, control_state.fd, EV_READ,
-	    control_accept, NULL);
-	event_add(&control_state.ev, NULL);
-	evtimer_set(&control_state.evt, control_accept, NULL);
-
-	return (0);
-}
-
-void
-control_accept(int listenfd, short event, void *bula)
-{
-	int			 connfd;
-	socklen_t		 len;
-	struct sockaddr_un	 sun;
-	struct ctl_conn		*c;
-
-	event_add(&control_state.ev, NULL);
-	if ((event & EV_TIMEOUT))
-		return;
-
-	len = sizeof(sun);
-	if ((connfd = accept4(listenfd, (struct sockaddr *)&sun, &len,
-	    SOCK_CLOEXEC | SOCK_NONBLOCK)) == -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(&control_state.ev);
-			evtimer_add(&control_state.evt, &evtpause);
-		} else if (errno != EWOULDBLOCK && errno != EINTR &&
-		    errno != ECONNABORTED)
-			log_warn("%s: accept4", __func__);
-		return;
-	}
-
-	if ((c = calloc(1, sizeof(struct ctl_conn))) == NULL) {
-		log_warn("%s: calloc", __func__);
-		close(connfd);
-		return;
-	}
-
-	imsg_init(&c->iev.ibuf, connfd);
-	c->iev.handler = control_dispatch_imsg;
-	c->iev.events = EV_READ;
-	event_set(&c->iev.ev, c->iev.ibuf.fd, c->iev.events,
-	    c->iev.handler, &c->iev);
-	event_add(&c->iev.ev, NULL);
-
-	TAILQ_INSERT_TAIL(&ctl_conns, c, entry);
-}
-
-struct ctl_conn *
-control_connbyfd(int fd)
-{
-	struct ctl_conn	*c;
-
-	TAILQ_FOREACH(c, &ctl_conns, entry) {
-		if (c->iev.ibuf.fd == fd)
-			break;
-	}
-
-	return (c);
-}
-
-struct ctl_conn *
-control_connbypid(pid_t pid)
-{
-	struct ctl_conn	*c;
-
-	TAILQ_FOREACH(c, &ctl_conns, entry) {
-		if (c->iev.ibuf.pid == pid)
-			break;
-	}
-
-	return (c);
-}
-
-void
-control_close(int fd)
-{
-	struct ctl_conn	*c;
-
-	if ((c = control_connbyfd(fd)) == NULL) {
-		log_warnx("%s: fd %d: not found", __func__, fd);
-		return;
-	}
-
-	msgbuf_clear(&c->iev.ibuf.w);
-	TAILQ_REMOVE(&ctl_conns, c, entry);
-
-	event_del(&c->iev.ev);
-	close(c->iev.ibuf.fd);
-
-	/* Some file descriptors are available again. */
-	if (evtimer_pending(&control_state.evt, NULL)) {
-		evtimer_del(&control_state.evt);
-		event_add(&control_state.ev, NULL);
-	}
-
-	free(c);
-}
-
-void
-control_dispatch_imsg(int fd, short event, void *bula)
-{
-	struct ctl_conn	*c;
-	struct imsg	 imsg;
-	ssize_t		 n;
-	int		 verbose;
-
-	if ((c = control_connbyfd(fd)) == NULL) {
-		log_warnx("%s: fd %d: not found", __func__, fd);
-		return;
-	}
-
-	if (event & EV_READ) {
-		if (((n = imsg_read(&c->iev.ibuf)) == -1 && errno != EAGAIN) ||
-		    n == 0) {
-			control_close(fd);
-			return;
-		}
-	}
-	if (event & EV_WRITE) {
-		if (msgbuf_write(&c->iev.ibuf.w) <= 0 && errno != EAGAIN) {
-			control_close(fd);
-			return;
-		}
-	}
-
-	for (;;) {
-		if ((n = imsg_get(&c->iev.ibuf, &imsg)) == -1) {
-			control_close(fd);
-			return;
-		}
-		if (n == 0)
-			break;
-
-		switch (imsg.hdr.type) {
-		case IMSG_CTL_RELOAD:
-			listener_imsg_compose_main(imsg.hdr.type, 0, NULL, 0);
-			break;
-		case IMSG_CTL_LOG_VERBOSE:
-			if (IMSG_DATA_SIZE(imsg) != sizeof(verbose))
-				break;
-
-			/* Forward to all other processes. */
-			listener_imsg_compose_main(imsg.hdr.type, imsg.hdr.pid,
-			    imsg.data, IMSG_DATA_SIZE(imsg));
-
-			/* XXX: send to every client? */
-
-			memcpy(&verbose, imsg.data, sizeof(verbose));
-			log_setverbose(verbose);
-			break;
-		default:
-			log_debug("%s: error handling imsg %d", __func__,
-			    imsg.hdr.type);
-			break;
-		}
-		imsg_free(&imsg);
-	}
-
-	imsg_event_add(&c->iev);
-}
-
-int
-control_imsg_relay(struct imsg *imsg)
-{
-	struct ctl_conn	*c;
-
-	if ((c = control_connbypid(imsg->hdr.pid)) == NULL)
-		return (0);
-
-	return (imsg_compose_event(&c->iev, imsg->hdr.type, 0, imsg->hdr.pid,
-	    -1, imsg->data, IMSG_DATA_SIZE(*imsg)));
-}
blob - 81ce3ca4ad9e77106d6d9b9d2f3d4af999039bdc (mode 644)
blob + /dev/null
--- control.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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 CONTROL_H
-#define CONTROL_H
-
-#include "compat.h"
-
-int	control_init(const char *);
-int	control_listen(int fd);
-void	control_accept(int, short, void *);
-void	control_dispatch_imsg(int, short, void *);
-int	control_imsg_relay(struct imsg *);
-
-#endif
blob - 491222d3473842f9bcedd0560be0a98b6150472b (mode 644)
blob + /dev/null
--- ctl_parser.c
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (c) 2004 Esben Norby <norby@openbsd.org>
- * 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 <string.h>
-
-#include "ctl_parser.h"
-#include "kamid.h"
-
-enum token_type {
-	NOTOKEN,
-	ENDTOKEN,
-	KEYWORD,
-};
-
-
-struct token {
-	enum token_type		 type;
-	const char		*keyword;
-	int			 value;
-	const struct token	*next;
-};
-
-static const struct token t_main[];
-static const struct token t_log[];
-
-static const struct token t_main[] = {
-	{KEYWORD,	"reload",	RELOAD,		NULL},
-	{KEYWORD,	"log",		NONE,		t_log},
-	{ENDTOKEN,	"",		NONE,		NULL},
-};
-
-static const struct token t_log[] = {
-	{KEYWORD,	"verbose",	LOG_VERBOSE,	NULL},
-	{KEYWORD,	"brief",	LOG_BRIEF,	NULL},
-	{ENDTOKEN,	"",		NONE,		NULL},
-};
-
-static const struct token *match_token(const char *, const struct token *,
-    struct parse_result *);
-static void show_valid_args(const struct token *);
-
-struct parse_result *
-parse(int argc, char **argv)
-{
-	static struct parse_result	 res;
-	const struct token		*table = t_main;
-	const struct token		*match;
-
-	memset(&res, 0, sizeof(res));
-
-	while (argc >= 0) {
-		if ((match = match_token(argv[0], table, &res)) == NULL) {
-			fprintf(stderr, "valid commands/args:\n");
-			show_valid_args(table);
-			return NULL;
-		}
-
-		argc--;
-		argv++;
-
-		if (match->type == NOTOKEN || match->next == NULL)
-			break;
-
-		table = match->next;
-	}
-
-	if (argc > 0) {
-		fprintf(stderr, "superfluous argument: %s\n", argv[0]);
-		return NULL;
-	}
-
-	return &res;
-}
-
-static const struct token *
-match_token(const char *word, const struct token *table,
-    struct parse_result *res)
-{
-	size_t			 i, match;
-	const struct token	*t = NULL;
-
-	match = 0;
-
-	for (i = 0; table[i].type != ENDTOKEN; i++) {
-		switch (table[i].type) {
-		case NOTOKEN:
-			if (word == NULL || strlen(word) == 0) {
-				match++;
-				t = &table[i];
-			}
-			break;
-		case KEYWORD:
-			if (word != NULL && strncmp(word, table[i].keyword,
-			    strlen(word)) == 0) {
-				match++;
-				t = &table[i];
-				if (t->value)
-					res->action = t->value;
-			}
-			break;
-		case ENDTOKEN:
-			break;
-		}
-	}
-
-	if (match != 1) {
-		if (word == NULL)
-			fprintf(stderr, "missing argument:\n");
-		else if (match > 1)
-			fprintf(stderr, "ambiuous argument: %s\n", word);
-		else if (match < 1)
-			fprintf(stderr, "unknown argument: %s\n", word);
-		return NULL;
-	}
-
-	return t;
-}
-
-static void
-show_valid_args(const struct token *table)
-{
-	int i;
-
-	for (i = 0; table[i].type != ENDTOKEN; i++) {
-		switch (table[i].type) {
-		case NOTOKEN:
-			fprintf(stderr, "  <cr>\n");
-			break;
-		case KEYWORD:
-			fprintf(stderr, "  %s\n", table[i].keyword);
-			break;
-		case ENDTOKEN:
-			break;
-		}
-	}
-}
blob - 1c41496f316a505b67099be11f5f5d3deec51a7b (mode 644)
blob + /dev/null
--- ctl_parser.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (c) 2004 Esben Norby <norby@openbsd.org>
- * 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 CTL_PARSER_H
-#define CTL_PARSER_H
-
-enum actions {
-	NONE,
-	LOG_VERBOSE,
-	LOG_BRIEF,
-	RELOAD,
-};
-
-struct parse_result {
-	enum actions	action;
-};
-
-struct parse_result	*parse(int, char **);
-
-#endif
blob - 3a202598199dd05601178ee98af2b1bd2a8df942 (mode 644)
blob + /dev/null
--- ftp.c
+++ /dev/null
@@ -1,1011 +0,0 @@
-/*
- * 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 "compat.h"
-
-#include <sys/ioctl.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-
-#include <assert.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <netdb.h>
-#include <limits.h>
-#include <signal.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <syslog.h>
-#include <tls.h>
-#include <unistd.h>
-
-#if HAVE_LIBREADLINE
-#include <readline/readline.h>
-#include <readline/history.h>
-#endif
-
-#ifndef nitems
-#define nitems(_a)	(sizeof((_a)) / sizeof((_a)[0]))
-#endif
-
-#include "9pclib.h"
-#include "kamid.h"
-#include "utils.h"
-#include "log.h"
-
-/* flags */
-int		 tls;
-const char	*crtpath;
-const char	*keypath;
-
-/* state */
-struct tls_config	*tlsconf;
-struct tls		*ctx;
-int			 sock;
-struct evbuffer		*buf;
-struct evbuffer		*dirbuf;
-uint32_t		 msize;
-int			 bell;
-
-volatile sig_atomic_t	 resized;
-int			 tty_p;
-int			 tty_width;
-
-struct np_stat {
-	uint16_t	 type;
-	uint32_t	 dev;
-	struct qid	 qid;
-	uint32_t	 mode;
-	uint32_t	 atime;
-	uint32_t	 mtime;
-	uint64_t	 length;
-	char		*name;
-	char		*uid;
-	char		*gid;
-	char		*muid;
-};
-
-struct progress {
-	uint64_t	max;
-	uint64_t	done;
-};
-
-int pwdfid;
-
-#define ASSERT_EMPTYBUF() assert(EVBUFFER_LENGTH(buf) == 0)
-
-#if HAVE_LIBREADLINE
-static char *
-read_line(const char *prompt)
-{
-	char *line;
-
-again:
-	if ((line = readline(prompt)) == NULL)
-		return NULL;
-	/* XXX: trim spaces? */
-	if (*line == '\0') {
-		free(line);
-		goto again;
-	}
-
-	add_history(line);
-	return line;
-}
-#else
-static char *
-read_line(const char *prompt)
-{
-	char *ch, *line = NULL;
-	size_t linesize = 0;
-	ssize_t linelen;
-
-	printf("%s", prompt);
-	fflush(stdout);
-
-	linelen = getline(&line, &linesize, stdin);
-	if (linelen == -1)
-		return NULL;
-
-	if ((ch = strchr(line, '\n')) != NULL)
-		*ch = '\0';
-	return line;
-}
-#endif
-
-static void
-tty_resized(int signo)
-{
-	resized = 1;
-}
-
-static void __dead
-usage(int ret)
-{
-	fprintf(stderr, "usage: %s [-c] host[:port] [path]\n",
-	    getprogname());
-	fprintf(stderr, PACKAGE_NAME " suite version " PACKAGE VERSION "\n");
-	exit(ret);
-}
-
-static void
-do_send(void)
-{
-	const void	*buf;
-	size_t		 nbytes;
-	ssize_t		 r;
-
-	while (EVBUFFER_LENGTH(evb) != 0) {
-		buf = EVBUFFER_DATA(evb);
-		nbytes = EVBUFFER_LENGTH(evb);
-
-		if (ctx == NULL) {
-			r = write(sock, buf, nbytes);
-			if (r == 0 || r == -1)
-				errx(1, "EOF");
-		} else {
-			r = tls_write(ctx, buf, nbytes);
-			if (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT)
-				continue;
-			if (r == -1)
-				errx(1, "tls: %s", tls_error(ctx));
-		}
-
-		evbuffer_drain(evb, r);
-	}
-}
-
-static void
-mustread(void *d, size_t len)
-{
-	ssize_t r;
-
-	while (len != 0) {
-		if (ctx == NULL) {
-			r = read(sock, d, len);
-			if (r == 0 || r == -1)
-				errx(1, "EOF");
-		} else {
-			r = tls_read(ctx, d, len);
-			if (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT)
-				continue;
-			if (r == -1)
-				errx(1, "tls: %s", tls_error(ctx));
-		}
-
-		d += r;
-		len -= r;
-	}
-}
-
-static void
-recv_msg(void)
-{
-	uint32_t	len, l;
-	char		tmp[BUFSIZ];
-
-	mustread(&len, sizeof(len));
-	len = le32toh(len);
-	if (len < HEADERSIZE)
-		errx(1, "read message of invalid length %d", len);
-
-	len -= 4; /* skip the length just read */
-
-	while (len != 0) {
-		l = MIN(len, sizeof(tmp));
-		mustread(tmp, l);
-		len -= l;
-		evbuffer_add(buf, tmp, l);
-	}
-}
-
-static uint64_t
-np_read64(struct evbuffer *buf)
-{
-	uint64_t n;
-
-	evbuffer_remove(buf, &n, sizeof(n));
-	return le64toh(n);
-}
-
-static uint32_t
-np_read32(struct evbuffer *buf)
-{
-	uint32_t n;
-
-	evbuffer_remove(buf, &n, sizeof(n));
-	return le32toh(n);
-}
-
-static uint16_t
-np_read16(struct evbuffer *buf)
-{
-	uint16_t n;
-
-	evbuffer_remove(buf, &n, sizeof(n));
-	return le16toh(n);
-}
-
-static uint16_t
-np_read8(struct evbuffer *buf)
-{
-	uint8_t n;
-
-	evbuffer_remove(buf, &n, sizeof(n));
-	return n;
-}
-
-static char *
-np_readstr(struct evbuffer *buf)
-{
-	uint16_t	 len;
-	char		*str;
-
-	len = np_read16(buf);
-	assert(EVBUFFER_LENGTH(buf) >= len);
-
-	if ((str = calloc(1, len+1)) == NULL)
-		err(1, "calloc");
-	evbuffer_remove(buf, str, len);
-	return str;
-}
-
-static void
-np_read_qid(struct evbuffer *buf, struct qid *qid)
-{
-	assert(EVBUFFER_LENGTH(buf) >= QIDSIZE);
-
-	qid->type = np_read8(buf);
-	qid->vers = np_read32(buf);
-	qid->path = np_read64(buf);
-}
-
-static int
-np_read_stat(struct evbuffer *buf, struct np_stat *st)
-{
-	uint16_t size;
-
-	memset(st, 0, sizeof(*st));
-
-	size = np_read16(buf);
-	if (size > EVBUFFER_LENGTH(buf))
-		return -1;
-
-	st->type = np_read16(buf);
-	st->dev = np_read32(buf);
-	np_read_qid(buf, &st->qid);
-	st->mode = np_read32(buf);
-	st->atime = np_read32(buf);
-	st->mtime = np_read32(buf);
-	st->length = np_read64(buf);
-	st->name = np_readstr(buf);
-	st->uid = np_readstr(buf);
-	st->gid = np_readstr(buf);
-	st->muid = np_readstr(buf);
-
-	return 0;
-}
-
-static void
-expect(uint8_t type)
-{
-	uint8_t t;
-
-	t = np_read8(buf);
-	if (t == type)
-		return;
-
-	if (t == Rerror) {
-		char *err;
-
-		/* skip tag */
-		np_read16(buf);
-
-		err = np_readstr(buf);
-		errx(1, "expected %s, got error %s",
-		    pp_msg_type(type), err);
-	}
-
-	errx(1, "expected %s, got msg type %s",
-	    pp_msg_type(type), pp_msg_type(t));
-}
-
-static void
-expect2(uint8_t type, uint16_t tag)
-{
-	uint16_t t;
-
-	expect(type);
-
-	t = np_read16(buf);
-	if (t == tag)
-		return;
-
-	errx(1, "expected tag 0x%x, got 0x%x", tag, t);
-}
-
-static void
-do_version(void)
-{
-	char		*version;
-
-	tversion(VERSION9P, MSIZE9P);
-	do_send();
-	recv_msg();
-	expect2(Rversion, NOTAG);
-
-	msize = np_read32(buf);
-	version = np_readstr(buf);
-
-	if (msize > MSIZE9P)
-		errx(1, "got unexpected msize: %d", msize);
-	if (strcmp(version, VERSION9P))
-		errx(1, "unexpected 9p version: %s", version);
-
-	free(version);
-	ASSERT_EMPTYBUF();
-}
-
-static void
-do_attach(const char *path)
-{
-	const char *user;
-	struct qid qid;
-
-	if (path == NULL)
-		path = "/";
-	if ((user = getenv("USER")) == NULL)
-		user = "flan";
-
-	tattach(pwdfid, NOFID, user, path);
-	do_send();
-	recv_msg();
-	expect2(Rattach, iota_tag);
-	np_read_qid(buf, &qid);
-
-	ASSERT_EMPTYBUF();
-}
-
-static uint32_t
-do_open(uint32_t fid, uint8_t mode)
-{
-	struct qid qid;
-	uint32_t iounit;
-
-	topen(fid, mode);
-	do_send();
-	recv_msg();
-	expect2(Ropen, iota_tag);
-
-	np_read_qid(buf, &qid);
-	iounit = np_read32(buf);
-
-	ASSERT_EMPTYBUF();
-
-	return iounit;
-}
-
-static void
-do_clunk(uint32_t fid)
-{
-	tclunk(fid);
-	do_send();
-	recv_msg();
-	expect2(Rclunk, iota_tag);
-
-	ASSERT_EMPTYBUF();
-}
-
-static void
-dup_fid(int fid, int nfid)
-{
-	uint16_t nwqid;
-
-	twalk(fid, nfid, NULL, 0);
-	do_send();
-	recv_msg();
-	expect2(Rwalk, iota_tag);
-
-	nwqid = np_read16(buf);
-	assert(nwqid == 0);
-
-	ASSERT_EMPTYBUF();
-}
-
-static int
-walk_path(int fid, int newfid, const char *path, struct qid *qid)
-{
-	char *wnames[MAXWELEM], *p, *t;
-	size_t nwname, i;
-	uint16_t nwqid;
-
-	if ((p = strdup(path)) == NULL)
-		err(1, "strdup");
-	t = p;
-
-	/* strip initial ./ */
-	if (t[0] == '.' && t[1] == '/')
-		t += 2;
-
-	for (nwname = 0; nwname < nitems(wnames) &&
-	    (wnames[nwname] = strsep(&t, "/")) != NULL;) {
-		if (*wnames[nwname] != '\0')
-			nwname++;
-	}
-
-	twalk(fid, newfid, (const char **)wnames, nwname);
-	do_send();
-	recv_msg();
-	expect2(Rwalk, iota_tag);
-
-	nwqid = np_read16(buf);
-	assert(nwqid <= nwname);
-
-	/* consume all qids */
-	for (i = 0; i < nwname; ++i)
-		np_read_qid(buf, qid);
-
-	free(p);
-
-	return nwqid == nwname;
-}
-
-static void
-do_stat(int fid, struct np_stat *st)
-{
-	tstat(fid);
-	do_send();
-	recv_msg();
-	expect2(Rstat, iota_tag);
-
-	if (np_read_stat(buf, st) == -1)
-		errx(1, "invalid stat struct read");
-
-	ASSERT_EMPTYBUF();
-}
-
-static size_t
-do_read(int fid, uint64_t off, uint32_t count, void *data)
-{
-	uint32_t r;
-
-	tread(fid, off, count);
-	do_send();
-	recv_msg();
-	expect2(Rread, iota_tag);
-
-	r = np_read32(buf);
-	assert(r == EVBUFFER_LENGTH(buf));
-	assert(r <= count);
-	evbuffer_remove(buf, data, r);
-
-	ASSERT_EMPTYBUF();
-
-	return r;
-}
-
-static void
-draw_progress(const char *pre, const struct progress *p)
-{
-	struct winsize ws;
-	int i, l, w;
-	double perc;
-
-	perc = 100.0 * p->done / p->max;
-	if (!tty_p) {
-		fprintf(stderr, "%s: %d%%\n", pre, (int)perc);
-		return;
-	}
-
-	if (resized) {
-		resized = 0;
-
-		if (ioctl(0, TIOCGWINSZ, &ws) == -1)
-			return;
-		tty_width = ws.ws_col;
-	}
-	w = tty_width;
-
-	if (pre == NULL ||
-	    ((l = printf("\r%s ", pre)) == -1 || l >= w))
-		return;
-
-	w -= l + 2 + 5; /* 2 for |, 5 for percentage + \n */
-	if (w < 0) {
-		printf("%4d%%\n", (int)perc);
-		return;
-	}
-
-	printf("|");
-
-	l = w * MIN(100.0, perc) / 100.0;
-	for (i = 0; i < l; i++)
-		printf("*");
-	for (; i < w; i++)
-		printf(" ");
-	printf("|%4d%%", (int)perc);
-
-	fflush(stdout);
-}
-
-static int
-fetch_fid(int fid, const char *path)
-{
-	struct progress p = {0};
-	struct np_stat st;
-	size_t r;
-	int fd;
-	char buf[BUFSIZ];
-
-	do_stat(fid, &st);
-	do_open(fid, KOREAD);
-
-	if ((fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0644)) == -1) {
-		warn("can't open %s", path);
-		return -1;
-	}
-
-	p.max = st.length;
-	for (;;) {
-		size_t siz, off;
-		ssize_t nw;
-
-		r = do_read(fid, p.done, sizeof(buf), buf);
-		if (r == 0)
-			break;
-
-		siz = sizeof(buf);
-		for (off = 0; off < siz; off += nw)
-			if ((nw = write(fd, buf + off, siz - off)) == 0 ||
-			    nw == -1)
-				err(1, "write");
-
-		p.done += r;
-		draw_progress(path, &p);
-
-#if 0
-		/* throttle, for debugging purpose */
-		{
-			struct timespec ts = { 0, 500000000 };
-			nanosleep(&ts, NULL);
-		}
-#endif
-	}
-
-	putchar('\n');
-
-	close(fd);
-	do_clunk(fid);
-	return 0;
-}
-
-static void
-do_tls_connect(const char *host, const char *port)
-{
-	int handshake;
-
-	if ((tlsconf = tls_config_new()) == NULL)
-		fatalx("tls_config_new");
-	tls_config_insecure_noverifycert(tlsconf);
-	tls_config_insecure_noverifyname(tlsconf);
-	if (tls_config_set_keypair_file(tlsconf, crtpath, keypath) == -1)
-		fatalx("can't load certs (%s, %s)", crtpath, keypath);
-
-	if ((ctx = tls_client()) == NULL)
-		fatal("tls_client");
-	if (tls_configure(ctx, tlsconf) == -1)
-		fatalx("tls_configure: %s", tls_error(ctx));
-
-	if (tls_connect(ctx, host, port) == -1)
-		fatalx("can't connect to %s:%s: %s", host, port,
-		    tls_error(ctx));
-
-	for (handshake = 0; !handshake;) {
-		switch (tls_handshake(ctx)) {
-		case -1:
-			fatalx("tls_handshake: %s", tls_error(ctx));
-		case 0:
-			handshake = 1;
-			break;
-		}
-	}
-}
-
-static void
-do_ctxt_connect(const char *host, const char *port)
-{
-	struct addrinfo hints, *res, *res0;
-	int error, saved_errno;
-	const char *cause = NULL;
-
-	memset(&hints, 0, sizeof(hints));
-	hints.ai_family = AF_UNSPEC;
-	hints.ai_socktype = SOCK_STREAM;
-	error = getaddrinfo(host, port, &hints, &res0);
-	if (error)
-		errx(1, "%s", gai_strerror(error));
-
-	sock = -1;
-	for (res = res0; res != NULL; res = res->ai_next) {
-		sock = socket(res->ai_family, res->ai_socktype,
-		    res->ai_protocol);
-		if (sock == -1) {
-			cause = "socket";
-			continue;
-		}
-
-		if (connect(sock, res->ai_addr, res->ai_addrlen) == -1) {
-			cause = "connect";
-			saved_errno = errno;
-			close(sock);
-			errno = saved_errno;
-			sock = -1;
-			continue;
-		}
-
-		break;
-	}
-
-	if (sock == -1)
-		err(1, "%s", cause);
-	freeaddrinfo(res0);
-}
-
-static void
-do_connect(const char *connspec, const char *path)
-{
-	char *host, *colon;
-	const char *port;
-
-	host = xstrdup(connspec);
-	if ((colon = strchr(host, ':')) != NULL) {
-		*colon = '\0';
-		port = ++colon;
-	} else
-		port = "1337";
-
-	printf("connecting to %s:%s...", host, port);
-	fflush(stdout);
-
-	if (tls)
-		do_tls_connect(host, port);
-	else
-		do_ctxt_connect(host, port);
-
-	printf(" done!\n");
-
-	do_version();
-	do_attach(path);
-
-	free(host);
-}
-
-static void
-cmd_bell(int argc, const char **argv)
-{
-	if (argc == 0) {
-		bell = !bell;
-		if (bell)
-			puts("bell mode enabled");
-		else
-			puts("bell mode disabled");
-		return;
-	}
-
-	if (argc != 1)
-		goto usage;
-
-	if (!strcmp(*argv, "on")) {
-		bell = 1;
-		puts("bell mode enabled");
-		return;
-	}
-
-	if (!strcmp(*argv, "off")) {
-		bell = 0;
-		puts("bell mode disabled");
-		return;
-	}
-
-usage:
-	printf("bell [on | off]\n");
-}
-
-static void
-cmd_bye(int argc, const char **argv)
-{
-	log_warnx("bye\n");
-	exit(0);
-}
-
-static void
-cmd_cd(int argc, const char **argv)
-{
-	struct qid qid;
-	int nfid;
-
-	if (argc != 1) {
-		printf("usage: cd remote-path\n");
-		return;
-	}
-
-	nfid = pwdfid+1;
-	if (walk_path(pwdfid, nfid, argv[0], &qid) == -1 ||
-	    !(qid.type & QTDIR)) {
-		printf("can't cd %s\n", argv[0]);
-		do_clunk(nfid);
-	} else {
-		do_clunk(pwdfid);
-		pwdfid = nfid;
-	}
-}
-
-static void
-cmd_get(int argc, const char **argv)
-{
-	struct qid qid;
-	const char *l;
-	int nfid;
-
-	if (argc != 1 && argc != 2) {
-		printf("usage: get remote-file [local-file]\n");
-		return;
-	}
-
-	if (argc == 2)
-		l = argv[1];
-	else if ((l = strrchr(argv[0], '/')) != NULL)
-		l++; /* skip / */
-	else
-		l = argv[1];
-
-	nfid = pwdfid+1;
-	if (walk_path(pwdfid, nfid, argv[0], &qid) == -1) {
-		printf("can't fetch %s\n", argv[0]);
-		return;
-	}
-
-	if (qid.type != 0) {
-		printf("can't fetch %s\n", argv[0]);
-		do_clunk(nfid);
-		return;
-	}
-
-	fetch_fid(nfid, l);
-}
-
-static void
-cmd_lcd(int argc, const char **argv)
-{
-	const char *dir;
-
-	if (argc > 1) {
-		printf("lcd takes only one argument\n");
-		return;
-	}
-
-	if (argc == 1)
-		dir = *argv;
-
-	if (argc == 0 && (dir = getenv("HOME")) == NULL) {
-		printf("HOME is not defined\n");
-		return;
-	}
-
-	if (chdir(dir) == -1)
-		printf("cd: %s: %s\n", dir, strerror(errno));
-}
-
-static void
-cmd_lpwd(int argc, const char **argv)
-{
-	char path[PATH_MAX];
-
-	if (getcwd(path, sizeof(path)) == NULL) {
-		printf("lpwd: %s\n", strerror(errno));
-		return;
-	}
-
-	printf("%s\n", path);
-}
-
-static void
-cmd_ls(int argc, const char **argv)
-{
-	struct np_stat st;
-	uint64_t off = 0;
-	uint32_t len;
-	char fmt[FMT_SCALED_STRSIZE];
-
-	if (argc != 0) {
-		printf("ls don't take arguments (yet)\n");
-		return;
-	}
-
-	dup_fid(pwdfid, 1);
-	do_open(1, KOREAD);
-
-	evbuffer_drain(dirbuf, EVBUFFER_LENGTH(dirbuf));
-
-	for (;;) {
-		tread(1, off, BUFSIZ);
-		do_send();
-		recv_msg();
-		expect2(Rread, iota_tag);
-
-		len = np_read32(buf);
-		if (len == 0)
-			break;
-
-		evbuffer_add_buffer(dirbuf, buf);
-		off += len;
-
-		ASSERT_EMPTYBUF();
-	}
-
-	while (EVBUFFER_LENGTH(dirbuf) != 0) {
-		if (np_read_stat(dirbuf, &st) == -1)
-			errx(1, "invalid stat struct read");
-
-		if (fmt_scaled(st.length, fmt) == -1)
-			strlcpy(fmt, "xxx", sizeof(fmt));
-
-		printf("%4s %8s %s\n", pp_qid_type(st.qid.type), fmt, st.name);
-
-		free(st.name);
-		free(st.uid);
-		free(st.gid);
-		free(st.muid);
-	}
-
-	do_clunk(1);
-}
-
-static void
-cmd_verbose(int argc, const char **argv)
-{
-	if (argc == 0) {
-		log_setverbose(!log_getverbose());
-		if (log_getverbose())
-			puts("verbose mode enabled");
-		else
-			puts("verbose mode disabled");
-		return;
-	}
-
-	if (argc != 1)
-		goto usage;
-
-	if (!strcmp(*argv, "on")) {
-		log_setverbose(1);
-		puts("verbose mode enabled");
-		return;
-	}
-
-	if (!strcmp(*argv, "off")) {
-		log_setverbose(0);
-		puts("verbose mode disabled");
-		return;
-	}
-
-usage:
-	printf("verbose [on | off]\n");
-}
-
-static void
-excmd(int argc, const char **argv)
-{
-	struct cmd {
-		const char	*name;
-		void		(*fn)(int, const char **);
-	} cmds[] = {
-		{"bell",	cmd_bell},
-		{"bye",		cmd_bye},
-		{"cd",		cmd_cd},
-		{"get",		cmd_get},
-		{"lcd",		cmd_lcd},
-		{"lpwd",	cmd_lpwd},
-		{"ls",		cmd_ls},
-		{"quit",	cmd_bye},
-		{"verbose",	cmd_verbose},
-	};
-	size_t i;
-
-	if (argc == 0)
-		return;
-	for (i = 0; i < nitems(cmds); ++i) {
-		if (!strcmp(cmds[i].name, *argv)) {
-			cmds[i].fn(argc-1, argv+1);
-			return;
-		}
-	}
-
-	log_warnx("unknown command %s", *argv);
-}
-
-int
-main(int argc, char **argv)
-{
-	int	ch;
-
-	log_init(1, LOG_DAEMON);
-	log_setverbose(0);
-	log_procinit(getprogname());
-
-	while ((ch = getopt(argc, argv, "C:cK:")) != -1) {
-		switch (ch) {
-		case 'C':
-			crtpath = optarg;
-			break;
-		case 'c':
-			tls = 1;
-			break;
-		case 'K':
-			keypath = optarg;
-			break;
-		default:
-			usage(1);
-		}
-	}
-	argc -= optind;
-	argv += optind;
-
-	if (argc == 0)
-		usage(1);
-
-	if (isatty(1)) {
-		tty_p = 1;
-		resized = 1;
-		signal(SIGWINCH, tty_resized);
-	}
-
-	if ((evb = evbuffer_new()) == NULL)
-		fatal("evbuffer_new");
-
-	if ((buf = evbuffer_new()) == NULL)
-		fatal("evbuffer_new");
-
-	if ((dirbuf = evbuffer_new()) == NULL)
-		fatal("evbuferr_new");
-
-	do_connect(argv[0], argv[1]);
-
-	for (;;) {
-		int argc = 0;
-		char *line, *argv[16] = {0}, **ap;
-
-		if ((line = read_line("kamiftp> ")) == NULL)
-			break;
-
-		for (argc = 0, ap = argv; ap < &argv[15] &&
-		    (*ap = strsep(&line, " \t")) != NULL;) {
-			if (**ap != '\0')
-				ap++, argc++;
-		}
-		excmd(argc, (const char **)argv);
-
-		if (bell) {
-			printf("\a");
-			fflush(stdout);
-		}
-
-		free(line);
-	}
-
-	printf("\n");
-}
blob - b263ba9f41d13275d28d6a215aa1419302259a89 (mode 644)
blob + /dev/null
--- kamictl.8
+++ /dev/null
@@ -1,61 +0,0 @@
-.\" 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.
-.\"
-.Dd $Mdocdate: July 07 2021 $
-.Dt KAMICTL 8
-.Os
-.Sh NAME
-.Nm kamictl
-.Nd control the kamid daemon
-.Sh SYNOPSIS
-.Nm
-.Op Fl s Ar socket
-.Ar command
-.Op Ar argument ...
-.Sh DESCRIPTION
-The
-.Nm
-program controls the
-.Xr kamid 8
-daemon.
-.Pp
-The following options are available:
-.Bl -tag -width Ds
-.It Fl s Ar socket
-Use
-.Ar socket
-instead of the default
-.Pa /var/run/kamid.sock
-to communicate with
-.Xr kamid 8 .
-.El
-.Pp
-The following commands are available:
-.Bl -tag -width Ds
-.It Cm log brief
-Disable verbose debug logging.
-.It Cm log verbose
-Enable verbose debug logging.
-.It Cm reload
-Reload the configuration file.
-.El
-.Sh FILES
-.Bl -tag -width "/var/run/kamid.sockXX" -compact
-.It Pa /var/run/kamid.sock
-UNIX-domain socket used for communication with
-.Xr kamid 8 .
-.El
-.Sh SEE ALSO
-.Xr kamid.conf 5 ,
-.Xr kamid 8
blob - 1a0172f56920d0407171fc51a81795eb6bef5233 (mode 644)
blob + /dev/null
--- kamictl.c
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * 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 "compat.h"
-
-#include <sys/socket.h>
-#include <sys/un.h>
-
-#include <err.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <syslog.h>
-#include <unistd.h>
-
-#include "ctl_parser.h"
-#include "kamid.h"
-#include "log.h"
-
-__dead void	 usage(void);
-
-struct imsgbuf	*ibuf;
-
-__dead void
-usage(void)
-{
-	/*
-	 * XXX: this will print `kamid' if compat/getprogname.c is
-	 * used.
-	 */
-	fprintf(stderr, "usage: %s [-s socket] command [argument ...]\n",
-	    getprogname());
-	exit(1);
-}
-
-int
-main(int argc, char **argv)
-{
-	struct sockaddr_un	 sun;
-	struct parse_result	*res;
-	struct imsg		 imsg;
-	int			 ctl_sock;
-	int			 done = 0;
-	int			 n, verbose = 0;
-	int			 ch;
-	const char		*sockname;
-
-	log_init(1, LOG_DAEMON); /* Log to stderr. */
-
-	sockname = KD_SOCKET;
-	while ((ch = getopt(argc, argv, "s:")) != -1) {
-		switch (ch) {
-		case 's':
-			sockname = optarg;
-			break;
-		default:
-			usage();
-		}
-	}
-	argc -= optind;
-	argv += optind;
-
-	/* parse command line */
-	if ((res = parse(argc, argv)) == NULL)
-		exit(1);
-
-	/* connect to control socket */
-	if ((ctl_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
-		err(1, "socket");
-
-	memset(&sun, 0, sizeof(sun));
-	sun.sun_family = AF_UNIX;
-	strlcpy(sun.sun_path, sockname, sizeof(sun.sun_path));
-
-	if (connect(ctl_sock, (struct sockaddr*)&sun, sizeof(sun)) == -1)
-		err(1, "connect: %s", sockname);
-
-#ifdef __OpenBSD__
-	if (pledge("stdio", NULL) == -1)
-		err(1, "pledge");
-#endif
-
-	if ((ibuf = calloc(1, sizeof(*ibuf))) == NULL)
-		err(1, NULL);
-	imsg_init(ibuf, ctl_sock);
-	done = 0;
-
-	/* process user request */
-	switch (res->action) {
-	case LOG_VERBOSE:
-		verbose = 1;
-		/* fallthrough */
-	case LOG_BRIEF:
-		imsg_compose(ibuf, IMSG_CTL_LOG_VERBOSE, 0, 0, -1,
-		    &verbose, sizeof(verbose));
-		puts("logging request sent.");
-		done = 1;
-		break;
-	case RELOAD:
-		imsg_compose(ibuf, IMSG_CTL_RELOAD, 0, 0, -1, NULL, 0);
-		puts("reload request sent.");
-		done = 1;
-		break;
-	default:
-		usage();
-	}
-
-	imsg_flush(ibuf);
-
-	/*
-	 * Later we may add commands which requires a response.
-	 */
-	while (!done) {
-		if ((n = imsg_get(ibuf, &imsg)) == -1)
-			errx(1, "imsg_get error");
-		if (n == 0)
-			break;
-
-		switch (res->action) {
-		default:
-			break;
-		}
-		imsg_free(&imsg);
-	}
-	close(ctl_sock);
-	free(ibuf);
-
-	return 0;
-}
blob - e0f398aaf1fb243d4b11fbef4b3e08442fd9ea46 (mode 644)
blob + /dev/null
--- kamid.8
+++ /dev/null
@@ -1,81 +0,0 @@
-.\" 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.
-.\"
-.Dd $Mdocdate: July 07 2021 $
-.Dt KAMID 8
-.Os
-.Sh NAME
-.Nm kamid
-.Nd 9p file server daemon
-.Sh SYNOPSIS
-.Nm
-.Op Fl dnv
-.Op Fl D Ar macro Ns = Ns Ar value
-.Op Fl f Pa file
-.Op Fl s Pa socket
-.Sh DESCRIPTION
-.Nm
-is a 9p file server daemon.
-.Pp
-The options are as follows:
-.Bl -tag -width Ds
-.It Fl D Ar macro Ns = Ns Ar value
-Set a
-.Ar macro
-to a
-.Ar value .
-Macros can be referenced in the configuration files.
-.It Fl d
-Do not daemonize.
-If this option is specified,
-.Nm
-will run in the foreground and log to
-.Em stderr .
-.It Fl f Ar file
-specify an alternative configuration file.
-.It Fl n
-Configtest mode.
-Only check the configuration file for validity.
-.It Fl s Ar socket
-Use an alternate location for the default control socket.
-.It Fl v
-Produce more verbose output.
-.El
-.Sh FILES
-.Bl -tag -width "/var/run/kamid.sockXX" -compact
-.It Pa /etc/kamid.conf
-Default
-.Nm
-configuration file.
-.It Pa /var/run/kamid.sock
-UNIX-domain socket used for communication with
-.Xr kamictl 8 .
-.El
-.Sh SEE ALSO
-.Xr kami.conf 5 ,
-.Xr kamictl 8
-.Sh AUTHORS
-.An -nosplit
-The
-.Nm
-program was written by
-.An Omar Polo Aq Mt op@omarpolo.com .
-.Sh CAVEATS
-.Nm
-doesn't handle very well when a user directory is over multiple
-devices since it uses the inode of files to build the QID path field.
-.Pp
-Opening/creating a file with the
-.Dv OEXEC
-result in an error.
blob - 05f753529776da3a4945281f04bec846131da94e (mode 644)
blob + /dev/null
--- kamid.c
+++ /dev/null
@@ -1,626 +0,0 @@
-/*
- * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
- * Copyright (c) 2018 Florian Obser <florian@openbsd.org>
- * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org>
- * Copyright (c) 2004 Esben Norby <norby@openbsd.org>
- * 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 <sys/socket.h>
-#include <sys/wait.h>
-
-#include <arpa/inet.h>
-#include <netinet/in.h>
-
-#include <errno.h>
-#include <fcntl.h>
-#include <pwd.h>
-#include <signal.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <syslog.h>
-#include <unistd.h>
-
-#include "client.h"
-#include "control.h"
-#include "kamid.h"
-#include "listener.h"
-#include "log.h"
-#include "sandbox.h"
-#include "table.h"
-#include "utils.h"
-
-enum kd_process {
-	PROC_MAIN,
-	PROC_LISTENER,
-	PROC_CLIENTCONN,
-};
-
-const char	*saved_argv0;
-static int	 debug, nflag;
-int		 verbose;
-
-__dead void	usage(void);
-
-void		main_sig_handler(int, short, void *);
-void		main_dispatch_listener(int, short, void *);
-int		main_reload(void);
-int		main_imsg_send_config(struct kd_conf *);
-void		main_dispatch_listener(int, short, void *);
-__dead void	main_shutdown(void);
-
-static pid_t	start_child(enum kd_process, int, int, int);
-
-struct kd_conf		*main_conf;
-static struct imsgev	*iev_listener;
-const char		*conffile;
-pid_t			 listener_pid;
-uint32_t		 cmd_opts;
-
-__dead void
-usage(void)
-{
-	fprintf(stderr, "usage: %s [-dnv] [-f file] [-s socket]\n",
-	    getprogname());
-	exit(1);
-}
-
-int
-main(int argc, char **argv)
-{
-	struct event	 ev_sigint, ev_sigterm, ev_sighup;
-	int		 ch;
-	int		 listener_flag = 0, client_flag = 0;
-	int		 pipe_main2listener[2];
-	int		 control_fd;
-	const char	*csock;
-
-	conffile = KD_CONF_FILE;
-	csock = KD_SOCKET;
-
-	log_init(1, LOG_DAEMON);	/* Log to stderr until deamonized. */
-	log_setverbose(1);
-
-	saved_argv0 = argv[0];
-	if (saved_argv0 == NULL)
-		saved_argv0 = "kamid";
-
-	while ((ch = getopt(argc, argv, "D:df:nsT:v")) != -1) {
-		switch (ch) {
-		case 'D':
-			if (cmdline_symset(optarg) == -1)
-                                log_warnx("could not parse macro definition %s",
-					optarg);
-			break;
-		case 'd':
-			debug = 1;
-			break;
-		case 'f':
-			conffile = optarg;
-			break;
-		case 'n':
-			nflag = 1;
-			break;
-		case 's':
-			csock = optarg;
-			break;
-		case 'T':
-			switch (*optarg) {
-			case 'c':
-				client_flag = 1;
-				break;
-			case 'l':
-				listener_flag = 1;
-				break;
-			default:
-				fatalx("invalid process spec %c", *optarg);
-			}
-			break;
-		case 'v':
-			verbose = 1;
-			break;
-		default:
-			usage();
-		}
-	}
-
-	argc -= optind;
-	argv += optind;
-	if (argc > 0 || (listener_flag && client_flag))
-		usage();
-
-	if (client_flag)
-		client(debug, verbose);
-	else if (listener_flag)
-		listener(debug, verbose);
-
-	if ((main_conf = parse_config(conffile)) == NULL)
-		exit(1);
-
-	if (nflag) {
-		fprintf(stderr, "configuration OK\n");
-		exit(0);
-	}
-
-	/* Check for root privileges. */
-        if (geteuid())
-                fatalx("need root privileges");
-
-	/* Check for assigned daemon user. */
-	if (getpwnam(KD_USER) == NULL)
-		fatalx("unknown user %s", KD_USER);
-
-	log_init(debug, LOG_DAEMON);
-	log_setverbose(verbose);
-
-	if (!debug)
-		daemon(1, 0);
-
-	log_info("startup");
-
-	if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
-	    PF_UNSPEC, pipe_main2listener) == -1)
-		fatal("main2listener socketpair");
-
-	/* Start children. */
-	listener_pid = start_child(PROC_LISTENER, pipe_main2listener[1],
-	    debug, verbose);
-
-	log_procinit("main");
-
-	event_init();
-
-	/* Setup signal handler */
-	signal_set(&ev_sigint, SIGINT, main_sig_handler, NULL);
-	signal_set(&ev_sigterm, SIGTERM, main_sig_handler, NULL);
-	signal_set(&ev_sighup, SIGHUP, main_sig_handler, NULL);
-
-	signal_add(&ev_sigint, NULL);
-	signal_add(&ev_sigterm, NULL);
-	signal_add(&ev_sighup, NULL);
-
-	signal(SIGCHLD, SIG_IGN);
-	signal(SIGPIPE, SIG_IGN);
-
-	if ((iev_listener = malloc(sizeof(*iev_listener))) == NULL)
-		fatal(NULL);
-	imsg_init(&iev_listener->ibuf, pipe_main2listener[0]);
-	iev_listener->handler = main_dispatch_listener;
-
-	/* Setup event handlers for pipes to listener. */
-	iev_listener->events = EV_READ;
-	event_set(&iev_listener->ev, iev_listener->ibuf.fd,
-	    iev_listener->events, iev_listener->handler, iev_listener);
-	event_add(&iev_listener->ev, NULL);
-
-	if ((control_fd = control_init(csock)) == -1)
-		fatalx("control socket setup failed");
-
-	main_imsg_compose_listener(IMSG_CONTROLFD, control_fd, 0,
-	    NULL, 0);
-	main_imsg_send_config(main_conf);
-
-	sandbox_main();
-
-	event_dispatch();
-
-	main_shutdown();
-	return 0;
-}
-
-void
-main_sig_handler(int sig, short event, void *arg)
-{
-	/*
-	 * Normal signal handler rules don't apply because libevent
-	 * decouples for us.
-	 */
-
-	switch (sig) {
-	case SIGTERM:
-	case SIGINT:
-		main_shutdown();
-		break;
-	case SIGHUP:
-		if (main_reload() == -1)
-			log_warnx("configuration reload failed");
-		else
-			log_debug("configuration reloaded");
-		break;
-	default:
-		fatalx("unexpected signal %d", sig);
-	}
-}
-
-static inline struct table *
-auth_table_by_id(uint32_t id)
-{
-	struct kd_listen_conf *listen;
-
-	STAILQ_FOREACH(listen, &main_conf->listen_head, entry) {
-		if (listen->id == id)
-			return listen->auth_table;
-	}
-
-	return NULL;
-}
-
-static inline struct table *
-virtual_table_by_id(uint32_t id)
-{
-	struct kd_listen_conf *listen;
-
-	STAILQ_FOREACH(listen, &main_conf->listen_head, entry) {
-		if (listen->id == id)
-			return listen->virtual_table;
-	}
-
-	return NULL;
-}
-
-static inline struct table *
-userdata_table_by_id(uint32_t id)
-{
-	struct kd_listen_conf *listen;
-
-	STAILQ_FOREACH(listen, &main_conf->listen_head, entry) {
-		if (listen->id == id)
-			return listen->userdata_table;
-	}
-
-	return NULL;
-}
-
-static inline void
-do_auth_tls(struct imsg *imsg)
-{
-	char *username = NULL, *user = NULL, *home = NULL, *local_user;
-	struct passwd *pw;
-	struct table *auth, *virt, *userdata;
-	struct kd_auth_req kauth;
-	int p[2], free_home = 1;
-
-	if (sizeof(kauth) != IMSG_DATA_SIZE(*imsg))
-		fatal("wrong size for IMSG_AUTH_TLS: "
-		    "got %lu; want %lu", IMSG_DATA_SIZE(*imsg),
-		    sizeof(kauth));
-	memcpy(&kauth, imsg->data, sizeof(kauth));
-
-	if (memmem(kauth.hash, sizeof(kauth.hash), "", 1) == NULL)
-                fatal("non NUL-terminated hash received");
-
-	log_debug("tls id=%u hash=%s", kauth.listen_id, kauth.hash);
-
-	if ((auth = auth_table_by_id(kauth.listen_id)) == NULL)
-		fatal("request for invalid listener id %d", imsg->hdr.pid);
-
-	virt = virtual_table_by_id(kauth.listen_id);
-	userdata = userdata_table_by_id(kauth.listen_id);
-
-	if (table_lookup(auth, kauth.hash, &username) == -1) {
-		log_warnx("login failed for hash %s", kauth.hash);
-		goto err;
-	}
-
-	if (virt != NULL && table_lookup(virt, username, &user) == -1) {
-		log_warnx("virtual lookup failed for user %s", username);
-		goto err;
-	}
-
-	/* the local user */
-	local_user = user != NULL ? user : username;
-
-	if (user != NULL)
-		log_debug("virtual user %s matched local user %s",
-		    username, user);
-	else
-		log_debug("matched local user %s", username);
-
-	if (userdata != NULL && table_lookup(userdata, username, &home)
-	    == -1) {
-		log_warnx("userdata lookup failed for user %s", username);
-		goto err;
-	} else if (userdata == NULL) {
-		if ((pw = getpwnam(local_user)) == NULL) {
-			log_warnx("getpwnam(%s) failed", local_user);
-			goto err;
-		}
-
-		free_home = 0;
-		home = pw->pw_dir;
-	}
-
-	if (user != NULL)
-		log_debug("matched home %s for virtual user %s",
-		    home, username);
-	else
-		log_debug("matched home %s for local user %s",
-		    home, username);
-
-	if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK,
-	    PF_UNSPEC, p) == -1)
-		fatal("socketpair");
-
-	start_child(PROC_CLIENTCONN, p[1], debug, verbose);
-
-	main_imsg_compose_listener(IMSG_AUTH, p[0], imsg->hdr.peerid,
-	    local_user, strlen(local_user)+1);
-	main_imsg_compose_listener(IMSG_AUTH_DIR, -1, imsg->hdr.peerid,
-	    home, strlen(home)+1);
-
-	free(username);
-	free(user);
-	if (free_home)
-		free(home);
-	return;
-
-err:
-	free(username);
-	free(user);
-	if (free_home)
-		free(home);
-	main_imsg_compose_listener(IMSG_AUTH, -1, imsg->hdr.peerid,
-	    NULL, 0);
-}
-
-void
-main_dispatch_listener(int fd, short event, void *d)
-{
-	struct imsgev	*iev = d;
-	struct imsgbuf	*ibuf;
-	struct imsg	 imsg;
-	ssize_t		 n;
-	int		 shut = 0;
-
-	ibuf = &iev->ibuf;
-
-	if (event & EV_READ) {
-		if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
-			fatal("imsg_read error");
-		if (n == 0)	/* Connection closed. */
-			shut = 1;
-	}
-	if (event & EV_WRITE) {
-		if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
-			fatal("msgbuf_write");
-		if (n == 0)	/* Connection closed. */
-			shut = 1;
-	}
-
-	for (;;) {
-		if ((n = imsg_get(ibuf, &imsg)) == -1)
-			fatal("imsg_get");
-		if (n == 0)	/* No more messages. */
-			break;
-
-		switch (imsg.hdr.type) {
-		case IMSG_AUTH_TLS:
-			do_auth_tls(&imsg);
-			break;
-		default:
-			log_debug("%s: error handling imsg %d", __func__,
-				imsg.hdr.type);
-			break;
-		}
-		imsg_free(&imsg);
-	}
-	if (!shut)
-		imsg_event_add(iev);
-	else {
-		/* This pipe is dead.  Remove its event handler. */
-		event_del(&iev->ev);
-		event_loopexit(NULL);
-	}
-}
-
-int
-main_reload(void)
-{
-	struct kd_conf *xconf;
-
-	if ((xconf = parse_config(conffile)) == NULL)
-		return -1;
-
-	if (main_imsg_send_config(xconf) == -1)
-		return -1;
-
-	merge_config(main_conf, xconf);
-
-	return 0;
-}
-
-static inline int
-make_socket_for(struct kd_listen_conf *l)
-{
-	struct sockaddr_in	addr4;
-	size_t			len;
-	int			fd, v;
-
-	memset(&addr4, 0, sizeof(addr4));
-	addr4.sin_family = AF_INET;
-	addr4.sin_port = htons(l->port);
-	addr4.sin_addr.s_addr = INADDR_ANY;
-
-	if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
-		fatal("socket");
-
-	v = 1;
-	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &v, sizeof(v)) == -1)
-		fatal("setsockopt(SO_REUSEADDR)");
-
-	v = 1;
-	if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &v, sizeof(v)) == -1)
-		fatal("setsockopt(SO_REUSEPORT)");
-
-	len = sizeof(addr4);
-	if (bind(fd, (struct sockaddr *)&addr4, len) == -1)
-		fatal("bind(%s, %d)", l->iface, l->port);
-
-	if (listen(fd, 16) == -1)
-		fatal("l(%s, %d)", l->iface, l->port);
-
-	return fd;
-}
-
-int
-main_imsg_send_config(struct kd_conf *xconf)
-{
-	struct kd_pki_conf *pki;
-	struct kd_listen_conf *listen;
-
-#define SEND(type, fd, data, len) do {					\
-		if (main_imsg_compose_listener(type, fd, 0, data, len)	\
-		    == -1)						\
-			return -1;					\
-	} while (0)
-
-	/* Send fixed part of config to children. */
-	SEND(IMSG_RECONF_CONF, -1, xconf, sizeof(*xconf));
-
-	STAILQ_FOREACH(pki, &xconf->pki_head, entry) {
-		log_debug("sending pki %s", pki->name);
-		SEND(IMSG_RECONF_PKI, -1, pki->name, sizeof(pki->name));
-		SEND(IMSG_RECONF_PKI_CERT, -1, pki->cert, pki->certlen);
-		SEND(IMSG_RECONF_PKI_KEY, -1, pki->key, pki->keylen);
-	}
-
-	STAILQ_FOREACH(listen, &xconf->listen_head, entry) {
-		log_debug("sending listen on port %d", listen->port);
-		SEND(IMSG_RECONF_LISTEN, make_socket_for(listen), listen,
-		    sizeof(*listen));
-	}
-
-	SEND(IMSG_RECONF_END, -1, NULL, 0);
-	return 0;
-
-#undef SEND
-}
-
-void
-merge_config(struct kd_conf *conf, struct kd_conf *xconf)
-{
-	/* do stuff... */
-
-	free(xconf);
-}
-
-struct kd_conf *
-config_new_empty(void)
-{
-	struct kd_conf *xconf;
-
-	if ((xconf = calloc(1, sizeof(*xconf))) == NULL)
-		fatal(NULL);
-
-	/* set default values */
-
-	return xconf;
-}
-
-void
-config_clear(struct kd_conf *conf)
-{
-	struct kd_conf *xconf;
-
-	/* Merge current config with an empty one. */
-	xconf = config_new_empty();
-	merge_config(conf, xconf);
-
-	free(conf);
-}
-
-__dead void
-main_shutdown(void)
-{
-	pid_t	pid;
-	int	status;
-
-	/* close pipes. */
-        config_clear(main_conf);
-
-	log_debug("waiting for children to terminate");
-	do {
-		pid = wait(&status);
-		if (pid == -1) {
-			if (errno != EINTR && errno != ECHILD)
-				fatal("wait");
-		} else if (WIFSIGNALED(status))
-			log_warnx("%s terminated; signal %d",
-			    (pid == listener_pid) ? "logger" : "clientconn",
-			    WTERMSIG(status));
-	} while (pid != -1 || (pid == -1 && errno == EINTR));
-
-	free(iev_listener);
-
-	log_info("terminating");
-	exit(0);
-}
-
-static pid_t
-start_child(enum kd_process p, int fd, int debug, int verbose)
-{
-	const char	*argv[5];
-	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(F_SETFD, 0) == -1)
-		fatal("cannot setup imsg fd");
-
-	argv[argc++] = saved_argv0;
-	switch (p) {
-	case PROC_MAIN:
-		fatalx("Can not start main process");
-	case PROC_LISTENER:
-		argv[argc++] = "-Tl";
-		break;
-	case PROC_CLIENTCONN:
-		argv[argc++] = "-Tc";
-		break;
-	}
-	if (debug)
-		argv[argc++] = "-d";
-	if (verbose)
-		argv[argc++] = "-v";
-	argv[argc++] = NULL;
-
-	/* really? */
-	execvp(saved_argv0, (char *const *)argv);
-	fatal("execvp");
-}
-
-int
-main_imsg_compose_listener(int type, int fd, uint32_t peerid,
-    const void *data, uint16_t datalen)
-{
-	if (iev_listener)
-		return imsg_compose_event(iev_listener, type, peerid, 0,
-		    fd, data, datalen);
-	else
-		return -1;
-}
blob - fd14ab7c7c727a8d7d30270434c294902e08c6e2 (mode 644)
blob + /dev/null
--- kamid.conf.5
+++ /dev/null
@@ -1,131 +0,0 @@
-.\" 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.
-.\"
-.Dd $Mdocdate: December 14 2021 $
-.Dt KAMID.CONF 5
-.Os
-.Sh NAME
-.Nm kamid.conf
-.Nd 9p file server daemon configuration file
-.Sh DESCRIPTION
-.Nm
-is the configuration file for the 9p file server daemon
-.Xr kamid 8 .
-.Pp
-The format of the configuration file is fairly flexible.
-The current line can be extended over multiple lines using a backslash
-.Pq Sq \e .
-Comments can be put anywhere in the file using a hash mark
-.Pq Sq # ,
-and extend to the end of the current line.
-Care should be taken when commenting out multi-line text: the comment is
-effective until the end of the entire block.
-Arguments names not beginning with a letter, digit, or underscore, as
-well as reserved words
-(such as
-.Ic listen ,
-.Ic pki
-and
-.Ic table )
-must be quoted.
-Arguments containing whitespace should be surrounded by double quotes
-.Pq \&" .
-.Pp
-Macros can be defined that are later expanded in context.
-Macro names must start with a letter, digit, or underscore, and may
-contain any of those characters, but may not be reserved words.
-Macros are not expanded inside quotes.
-For example:
-.Bd -literal -offset indent
-lan_addr = "192.168.0.1"
-listen on $lan_addr
-listen on $lan_addr tls auth <users>
-.Ed
-.Pp
-Additional configuration files can be included with the
-.Ic include
-keyword, for example:
-.Bd -literal -offset indent
-include "/etc/kamid.conf.local"
-.Ed
-.Pp
-The syntax of
-.Nm
-is described below.
-.Bl -tag -width Ds
-.It Ic listen Op Ar options...
-The options are as follows:
-.Bl -tag -width Ds
-.It Ic on Ar address Ic port Ar number
-Listen on the
-.Ar address
-for incoming connection on the given port
-.Ar number .
-.Ar address
-can be an IP address or a domain name.
-.It Ic tls Ic pki Ar name
-Use the tls certificate
-.Ar name
-previously defined with the
-.Ic pki
-rule.
-.It Ic auth Pf < Ar table Ns >
-Use the given authentication
-.Ar table
-to authorize the clients.
-.It Ic userdata Pf < Ar table Ns >
-Maps user
-.Pq virtuals or not
-to their exported tree.
-By default the user home directory obtained with
-.Xr getpwnam 3
-is used.
-.It Ic virtual Pf < Ar table Ns >
-Maps virtual users to local user.
-.El
-.It Ic pki Ar pkiname Ic cert Ar certfile
-Associate certificate file
-.Ar certfile
-with pki entry
-.Ar pkiname .
-The pki entry defines a keypair configuration that can be referenced in
-listener rules.
-.It Ic pki Ar pkiname Ic key Ar keyfile
-Associate the key located in
-.Ar keyfile
-with pki entry
-.Ar pkiname .
-.\" TODO: document the other syntax for the table
-.It Ic table Ar name Brq Ar value Cm => Ar value Oo , Ar ... Oc
-Tables provide additional configuration information for
-.Xr kamid 8
-in the form of key-value mappings.
-.Pp
-Declare a mapping table containing the given static
-.Ar key Ns Pf - Ar value
-pairs.
-.El
-.Sh EXAMPLES
-A sample configuration file:
-.Bd -literal -offset indent
-pki localhost cert "/etc/ssl/localhost.crt"
-pki localhost key "/etc/ssl/private/localhost.key"
-
-table users { "SHA256:..." => "op" }
-
-listen on localhost port 1337 tls pki localhost auth <users>
-.Ed
-.Sh SEE ALSO
-.Xr kamictl 8 ,
-.Xr kamid 8
blob - 9675e6ac175f820b3eec23853740698a562b017b (mode 644)
blob + /dev/null
--- kamid.h
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef KAMID_H
-#define KAMID_H
-
-#include "compat.h"
-
-#include <limits.h>
-#include <stdint.h>
-#include <tls.h>
-
-/* TODO: make these customizable */
-#define KD_CONF_FILE		"/etc/kamid.conf"
-#define KD_USER			"_kamid"
-#define KD_SOCKET		"/var/run/kamid.sock"
-
-#define IMSG_DATA_SIZE(imsg)	((imsg).hdr.len - IMSG_HEADER_SIZE)
-
-#define MIN(a, b) ((a) < (b) ? (a) : (b))
-
-struct imsgev {
-	struct imsgbuf	 ibuf;
-	void		(*handler)(int, short, void *);
-	struct event	 ev;
-	short		 events;
-};
-
-enum imsg_type {
-	IMSG_NONE,
-	IMSG_CTL_LOG_VERBOSE,
-	IMSG_CTL_RELOAD,
-	IMSG_CONTROLFD,
-	IMSG_STARTUP,
-	IMSG_RECONF_CONF,
-	IMSG_RECONF_PKI,
-	IMSG_RECONF_PKI_CERT,
-	IMSG_RECONF_PKI_KEY,
-	IMSG_RECONF_LISTEN,
-	IMSG_RECONF_END,
-	IMSG_AUTH,
-	IMSG_AUTH_DIR,
-	IMSG_AUTH_TLS,
-	IMSG_CONN_GONE,
-	IMSG_BUF,
-	IMSG_MSIZE,
-	IMSG_CLOSE,
-};
-
-struct kd_options_conf {
-	/* ... */
-};
-
-enum table_type {
-	T_NONE		= 0,
-	T_HASH		= 0x01,
-};
-
-struct table {
-	char			 t_name[LINE_MAX];
-	enum table_type		 t_type;
-	char			 t_path[PATH_MAX];
-	void			*t_handle;
-	struct table_backend	*t_backend;
-};
-
-struct table_backend {
-	const char	*name;
-	int		(*open)(struct table *);
-	int		(*add)(struct table *, const char *, const char *);
-	int		(*lookup)(struct table *, const char *, char **);
-	void		(*close)(struct table *);
-};
-
-/* table_static.c */
-extern struct table_backend table_static;
-
-#define L_NONE	0x0
-#define L_TLS	0x1
-struct kd_listen_conf {
-	STAILQ_ENTRY(kd_listen_conf)	 entry;
-	uint32_t			 id;
-	uint32_t			 flags;
-	int				 fd;
-	char				 iface[LINE_MAX];
-	uint16_t			 port;
-
-	/* certificate hash => (virtual) user */
-	struct table			*auth_table;
-
-	/* virtual user => local user */
-	struct table			*virtual_table;
-
-	/* (virtual) user => export directory */
-	struct table			*userdata_table;
-
-	char				 pki[LINE_MAX];
-	struct event			 ev;
-	struct tls			*ctx;
-};
-
-struct kd_pki_conf {
-	STAILQ_ENTRY(kd_pki_conf)	 entry;
-	char				 name[LINE_MAX];
-	uint8_t				*cert;
-	size_t				 certlen;
-	uint8_t				*key;
-	size_t				 keylen;
-	struct tls_config		*tlsconf;
-};
-
-struct kd_tables_conf {
-	STAILQ_ENTRY(kd_tables_conf)	 entry;
-	struct table			*table;
-};
-
-struct kd_conf {
-	struct kd_options_conf					 kd_options;
-	STAILQ_HEAD(kd_pki_conf_head, kd_pki_conf)		 pki_head;
-	STAILQ_HEAD(kd_tables_conf_head, kd_tables_conf)	 table_head;
-	STAILQ_HEAD(kd_listen_conf_head, kd_listen_conf)	 listen_head;
-};
-
-struct kd_auth_req {
-	uint32_t	listen_id;
-	char		hash[128+1];
-};
-
-/*
- * 9p message header.
- *
- * The message itself is len bytes long (counting the whole header
- * too.)
- */
-struct np_msg_header {
-	uint32_t	len;
-	uint8_t		type;
-	uint16_t	tag;
-};
-
-struct qid {
-	uint64_t		 path;
-	uint32_t		 vers;
-	uint8_t			 type;
-};
-
-/* useful constants */
-#define HEADERSIZE	(4 + 1 + 2)
-#define	VERSION9P	"9P2000"
-#define MSIZE9P		((uint32_t)4*1024*1024)
-#define NOTAG		((uint16_t)~0U)
-#define NOFID		((uint32_t)~0U)
-#define NOUID		(-1)
-#define QIDSIZE		13
-#define MAXWELEM	16
-
-#define NPSTATSIZ(namlen, uidnam, gidnam, unam) \
-	(6 + QIDSIZE + 20 + 2 + namlen + 2 + uidnam + 2 + gidnam + 2 + unam)
-
-/* bits in Qid.type */
-#define QTDIR		0x80		/* type bit for directories */
-#define QTAPPEND	0x40		/* type bit for append only files */
-#define QTEXCL		0x20		/* type bit for exclusive use files */
-#define QTMOUNT		0x10		/* type bit for mounted channel */
-#define QTAUTH		0x08		/* type bit for authentication file */
-#define QTTMP		0x04		/* type bit for non-backed-up file */
-#define QTSYMLINK	0x02		/* type bit for symbolic link */
-#define QTFILE		0x00		/* type bits for plain file */
-
-/* Topen mode/flags */
-#define KOREAD		0x00
-#define KOWRITE		0x01
-#define KORDWR		0x02
-#define KOEXEC		0x03
-#define KOTRUNC		0x10
-#define KORCLOSE	0x40
-
-/* 9p message types */
-enum {
-	Treaddir =	40,	/* .L */
-	Rreaddir,
-
-	Tversion =	100,
-	Rversion,
-	Tauth =		102,
-	Rauth,
-	Tattach =	104,
-	Rattach,
-	Terror =	106,	/* illegal */
-	Rerror,
-	Tflush =	108,
-	Rflush,
-	Twalk =		110,
-	Rwalk,
-	Topen =		112,
-	Ropen,
-	Tcreate =	114,
-	Rcreate,
-	Tread =		116,
-	Rread,
-	Twrite =	118,
-	Rwrite,
-	Tclunk =	120,
-	Rclunk,
-	Tremove =	122,
-	Rremove,
-	Tstat =		124,
-	Rstat,
-	Twstat =	126,
-	Rwstat,
-	Tmax,
-
-	/*
-	 * plan9ports' include/fcall.h also has a
-	 *
-	 *	Topenfd = 98,
-	 *	Ropenfd,
-	 *
-	 * which it's not mentioned in the 9p "rfc" over at
-	 * 9p.cat-v.org.  Ignoring that for now.
-	 */
-};
-
-/* kamid.c */
-extern int verbose;
-int	main_imsg_compose_listener(int, int, uint32_t, const void *, uint16_t);
-void	merge_config(struct kd_conf *, struct kd_conf *);
-
-struct kd_conf	*config_new_empty(void);
-void		 config_clear(struct kd_conf *);
-
-/* parse.y */
-struct kd_conf	*parse_config(const char *);
-int		 cmdline_symset(char *);
-
-#endif
blob - bbb82ddbd18431c0c4cc0cc05d9e9273e5bf4d26 (mode 644)
blob + /dev/null
--- kamiftp.1
+++ /dev/null
@@ -1,80 +0,0 @@
-.\" 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.
-.\"
-.Dd $Mdocdate: December 14 2021 $
-.Dt KAMIFTP 1
-.Os
-.Sh NAME
-.Nm kamiftp
-.Nd 9p client
-.Sh SYNOPSIS
-.Nm
-.Op Fl c
-.Op Fl C Ar cert
-.Op Fl K Ar key
-.Ar host Op Ar path
-.Sh DESCRIPTION
-.Nm
-is a
-.Xr 9p 7
-client.
-.Pp
-The options are as follows:
-.Bl -tag -width Ds
-.It Fl c
-Use TLS for the connection.
-.Fl K
-and
-.Fl C
-are mandatory if
-.Fl c
-is used.
-.It Fl C Ar certificate
-Specify the path to the client
-.Ar certificate
-to be use during the TLS handsahke.
-.It Fl K Ar key
-Specify the path to the client certificate
-.Ar key
-to be used during the TLS handshake.
-.El
-.Pp
-The following commands are recognized by
-.Nm :
-.Bl -tag -width Ds
-.It Ic bell Oo Cm on | off Oc
-Request terminal to sound a bell after each command.
-Without arguments toggle the current state.
-.It Ic bye
-Terminate the session.
-Synomym of
-.Ic quit .
-.It Ic ls
-List the file in the current directory
-.It Ic quit
-Terminate the session.
-Synomym of
-.Ic bye .
-.It Ic verbose Oo Cm on | off Oc
-Print verbose information.
-Without arguments toggle the current state.
-.El
-.Sh SEE ALSO
-.Xr 9p 7 ,
-.Xr kamid 8
-.Sh AUTHORS
-The
-.Nm
-utility was written by
-.An Omar Polo Aq Mt op@omarpolo.com .
blob - fb0c86530732d462858c57359e8a544d794d6b06 (mode 644)
blob + /dev/null
--- kamirepl.1
+++ /dev/null
@@ -1,152 +0,0 @@
-.\" 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.
-.\"
-.Dd $Mdocdate: December 16 2021 $
-.Dt KAMIREPL 1
-.Os
-.Sh NAME
-.Nm kamirepl
-.Nd 9p repl client
-.Sh SYNOPSIS
-.Nm
-.Op Fl chv
-.Op Fl C Ar cert
-.Op Fl H Ar host
-.Op Fl K Ar key
-.Op Fl P Ar port
-.Sh DESCRIPTION
-.Nm
-is a 
-.Xr 9p 7
-repl client.
-.Pp
-The optinos are as follows:
-.Bl -tag -width tenletters
-.It Fl C Ar cert
-Path to the TLS client certificate to use.
-.It Fl c
-Use TLS for the connection.
-.Fl C
-and
-.Fl K
-are mandatory if used.
-.It Fl H Ar host
-Hostname of the file server.
-.It Fl h
-Display usage and exit.
-.It Fl K Ar key
-Path to the TLS client certificate private key.
-.It Fl P Ar port
-Port number to connect to.
-.It Fl v
-Verbose logging.
-.El
-.Pp
-The interactive commands are
-.Bl -tag -width Ds
-.It Ic version Op Ar version-string
-.Ar version-string
-is
-.Dq 9P2000
-by default.
-.It Ic attach Ar fid Ar uname Ar aname
-Request the file server to attach the file tree identified by
-.Ar aname
-to the specified
-.Ar fid
-number.
-.Ar aname
-is the identifier for the user.
-The afid used is implicitly NOFID.
-.It Ic clunk Ar fid
-Closes
-.Ar fid.
-.It Ic flush Ar oldtag
-Require the server to flush
-.Ar oldtag .
-.It Ic walk Ar fid Ar newfid Ar wnames...
-Do a walk from
-.Ar fid
-following
-.Ar wnames
-component and associating the reached file to
-.Ar newfid .
-.It Ic open Ar fid Ar mode Op Ar flag
-Prepare
-.Ar fid
-for I/O.
-.Ar mode
-can be one of
-.Sq read
-or
-.Sq r ,
-.Sq write
-or
-.Sq w ,
-.Sq readwrite
-or
-.Sq rdwr .
-Optionally,
-.Ar flag
-can be on of
-.Sq trunc
-to truncate the file or
-.Sq rclose
-to remove the file upon
-.Ic clunk .
-.It Ic create Ar fid Ar name Ar perm Ar mode
-Create the file
-.Ar name
-and open it with
-.Ar mode
-as the given
-.Ar fid.
-.Ar perm
-should be used to select the permissions of the file, but is currently
-unused.
-.It Ic read Ar fid Ar offset Ar count
-Issue a read request for the given
-.Ar fid ,
-which must have been prepared for I/O with
-.Ic open ,
-at
-.Ar offset
-and for
-.Ar count
-bytes.
-.It Ic write Ar fid Ar offset Ar content
-Writes
-.Ar content
-to
-.Ar fid
-starting at
-.Ar offset .
-.It Ic remove Ar fid
-Delete the file identified by
-.Ar fid
-and close it.
-Even in case of error,
-.Ar fid
-is clunked.
-.El
-.Sh SEE ALSO
-.Xr kamiftp 1
-.Xr 9p 7
-.Xr kamid 8
-.Sh AUTHORS
-.An -nosplit
-The
-.Nm
-utility was written by
-.An Omar Polo Aq Mt op@omarpolo.com .
blob - 504329156629b6374f7a18342d97b66bfbb16659 (mode 644)
blob + /dev/null
--- kamirepl.c
+++ /dev/null
@@ -1,1073 +0,0 @@
-/*
- * 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 "compat.h"
-
-#include <sys/types.h>
-#include <sys/socket.h>
-
-#include <netdb.h>
-
-#include <assert.h>
-#include <endian.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <signal.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <syslog.h>
-#include <tls.h>
-#include <unistd.h>
-
-#include "9pclib.h"
-#include "kamid.h"
-#include "log.h"
-#include "utils.h"
-
-#define DEBUG_PACKETS 0
-
-#define PROMPT "=% "
-
-/* flags */
-int			 verbose;
-int			 tls;
-const char		*keypath;
-const char		*crtpath;
-const char		*host;
-const char		*port;
-
-/* state */
-struct tls_config	*tlsconf;
-struct tls		*ctx;
-struct bufferevent	*bev, *inbev;
-
-static void __dead	 usage(int);
-
-static void		 sig_handler(int, short, void *);
-
-static int		 openconn(void);
-static void		 mark_nonblock(int);
-
-static void		 tls_readcb(int, short, void *);
-static void		 tls_writecb(int, short, void *);
-
-static void		 client_read(struct bufferevent *, void *);
-static void		 client_write(struct bufferevent *, void *);
-static void		 client_error(struct bufferevent *, short, void *);
-
-static void		 repl_read(struct bufferevent *, void *);
-static void		 repl_error(struct bufferevent *, short, void *);
-
-static void		 excmd_version(const char **, int);
-static void		 excmd_attach(const char **, int);
-static void		 excmd_clunk(const char **, int);
-static void		 excmd_flush(const char **, int);
-static void		 excmd_walk(const char ** , int);
-static void		 excmd_open(const char ** , int);
-static void		 excmd_create(const char ** , int);
-static void		 excmd_read(const char ** , int);
-static void		 excmd_write(const char **, int);
-static void		 excmd(const char **, int);
-
-static void		 pp_qid(const uint8_t *, uint32_t);
-static void		 pp_msg(uint32_t, uint8_t, uint16_t, const uint8_t *);
-static void		 handle_9p(const uint8_t *, size_t);
-static void		 clr(void);
-static void		 prompt(void);
-
-static void __dead
-usage(int ret)
-{
-	fprintf(stderr,
-	    "usage: %s [-chv] [-C crt] [-K key] [-H host] [-P port]\n",
-	    getprogname());
-	fprintf(stderr, PACKAGE_NAME " suite version " PACKAGE_VERSION "\n");
-	exit(ret);
-}
-
-static void
-sig_handler(int sig, short event, void *d)
-{
-	/*
-	 * Normal signal handler rules don't apply because libevent
-	 * decouples for us.
-	 */
-
-	switch (sig) {
-	case SIGINT:
-	case SIGTERM:
-		clr();
-		log_warnx("Shutting down...");
-		event_loopbreak();
-		return;
-	default:
-		fatalx("unexpected signal %d", sig);
-	}
-}
-
-static int
-openconn(void)
-{
-	struct addrinfo hints, *res, *res0;
-	int error;
-	int save_errno;
-	int s;
-	const char *cause = NULL;
-
-	memset(&hints, 0, sizeof(hints));
-	hints.ai_family = AF_UNSPEC;
-	hints.ai_socktype = SOCK_STREAM;
-	if ((error = getaddrinfo(host, port, &hints, &res0))) {
-		warnx("%s", gai_strerror(error));
-		return -1;
-	}
-
-	s = -1;
-	for (res = res0; res; res = res->ai_next) {
-		s = socket(res->ai_family, res->ai_socktype,
-		    res->ai_protocol);
-		if (s == -1) {
-			cause = "socket";
-			continue;
-		}
-
-		if (connect(s, res->ai_addr, res->ai_addrlen) == -1) {
-			cause = "connect";
-			save_errno = errno;
-			close(s);
-			errno = save_errno;
-			s = -1;
-			continue;
-		}
-
-		break;
-	}
-
-	freeaddrinfo(res0);
-
-	if (s == -1)
-		warn("%s", cause);
-
-	return s;
-}
-
-static void
-mark_nonblock(int fd)
-{
-	int flags;
-
-	if ((flags = fcntl(fd, F_GETFL)) == -1)
-		fatal("fcntl(F_GETFL)");
-	if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
-		fatal("fcntl(F_SETFL)");
-}
-
-static void
-tls_readcb(int fd, short event, void *d)
-{
-	struct bufferevent	*bufev = d;
-	char			 buf[IBUF_READ_SIZE];
-	int			 what = EVBUFFER_READ;
-	int			 howmuch = IBUF_READ_SIZE;
-	ssize_t			 ret;
-	size_t			 len;
-
-	if (event == EV_TIMEOUT) {
-		what |= EVBUFFER_TIMEOUT;
-		goto err;
-	}
-
-	if (bufev->wm_read.high != 0)
-		howmuch = MIN(sizeof(buf), bufev->wm_read.high);
-
-	switch (ret = tls_read(ctx, buf, howmuch)) {
-	case TLS_WANT_POLLIN:
-	case TLS_WANT_POLLOUT:
-		goto retry;
-	case -1:
-		what |= EVBUFFER_ERROR;
-		goto err;
-	}
-	len = ret;
-
-	if (len == 0) {
-		what |= EVBUFFER_EOF;
-		goto err;
-	}
-
-	if (evbuffer_add(bufev->input, buf, len) == -1) {
-		what |= EVBUFFER_ERROR;
-		goto err;
-	}
-
-	event_add(&bufev->ev_read, NULL);
-
-	len = EVBUFFER_LENGTH(bufev->input);
-	if (bufev->wm_read.low != 0 && len < bufev->wm_read.low)
-		return;
-	if (bufev->readcb != NULL)
-		(*bufev->readcb)(bufev, bufev->cbarg);
-	return;
-
-retry:
-	event_add(&bufev->ev_read, NULL);
-	return;
-
-err:
-	(*bufev->errorcb)(bufev, what, bufev->cbarg);
-}
-
-static void
-tls_writecb(int fd, short event, void *d)
-{
-	struct bufferevent	*bufev = d;
-	ssize_t			 ret;
-	size_t			 len;
-	short			 what = EVBUFFER_WRITE;
-	void			*data;
-
-	if (event == EV_TIMEOUT) {
-		what |= EVBUFFER_TIMEOUT;
-		goto err;
-	}
-
-	len = EVBUFFER_LENGTH(bufev->output);
-	if (len != 0) {
-		data = EVBUFFER_DATA(bufev->output);
-
-#if DEBUG_PACKETS
-		hexdump("outgoing msg", data, len);
-#endif
-
-		switch (ret = tls_write(ctx, data, len)) {
-		case TLS_WANT_POLLIN:
-		case TLS_WANT_POLLOUT:
-			goto retry;
-		case -1:
-			what |= EVBUFFER_ERROR;
-			goto err;
-		}
-		evbuffer_drain(bufev->output, ret);
-	}
-
-	if (EVBUFFER_LENGTH(bufev->output) != 0)
-		event_add(&bufev->ev_write, NULL);
-
-	if (bufev->writecb != NULL &&
-	    EVBUFFER_LENGTH(bufev->output) <= bufev->wm_write.low)
-		(*bufev->writecb)(bufev, bufev->cbarg);
-	return;
-
-retry:
-	event_add(&bufev->ev_write, NULL);
-	return;
-err:
-	(*bufev->errorcb)(bufev, what, bufev->cbarg);
-}
-
-static void
-client_read(struct bufferevent *bev, void *d)
-{
-	struct evbuffer	*src = EVBUFFER_INPUT(bev);
-	uint32_t	 len;
-	uint8_t		*data;
-
-	for (;;) {
-		if (EVBUFFER_LENGTH(src) < sizeof(len))
-			return;
-
-		data = EVBUFFER_DATA(src);
-
-		memcpy(&len, data, sizeof(len));
-		len = le32toh(len);
-
-		if (len < HEADERSIZE)
-			fatal("incoming message is too small! (%d bytes)",
-			    len);
-
-		if (len > EVBUFFER_LENGTH(src))
-			return;
-
-#if DEBUG_PACKETS
-		hexdump("incoming msg", data, len);
-#endif
-
-		handle_9p(data, len);
-		evbuffer_drain(src, len);
-	}
-}
-
-static void
-client_write(struct bufferevent *bev, void *data)
-{
-	return; /* nothing to do */
-}
-
-static void
-client_error(struct bufferevent *bev, short err, void *data)
-{
-	if (err & EVBUFFER_ERROR)
-		fatal("buffer event error");
-
-	if (err & EVBUFFER_EOF) {
-		clr();
-		log_info("EOF");
-		event_loopbreak();
-		return;
-	}
-
-	clr();
-	log_warnx("unknown event error");
-	event_loopbreak();
-}
-
-static void
-repl_read(struct bufferevent *bev, void *d)
-{
-	size_t		 len;
-	int		 argc;
-	const char	*argv[10], **ap;
-	char		*line;
-
-	line = evbuffer_readln(bev->input, &len, EVBUFFER_EOL_LF);
-	if (line == NULL)
-		return;
-
-	for (argc = 0, ap = argv; ap < &argv[9] &&
-	    (*ap = strsep(&line, " \t")) != NULL;) {
-		if (**ap != '\0')
-			ap++, argc++;
-	}
-
-	clr();
-	excmd(argv, argc);
-	prompt();
-
-	free(line);
-}
-
-static void
-repl_error(struct bufferevent *bev, short error, void *d)
-{
-	fatalx("an error occurred");
-}
-
-static inline void
-do_send(void)
-{
-	bufferevent_write_buffer(bev, evb);
-}
-
-/* version [version-str] */
-static void
-excmd_version(const char **argv, int argc)
-{
-	const char	*s;
-
-	s = VERSION9P;
-	if (argc == 2)
-		s = argv[1];
-
-	tversion(s, MSIZE9P);
-	do_send();
-}
-
-/* attach fid uname aname */
-static void
-excmd_attach(const char **argv, int argc)
-{
-	uint32_t	 fid;
-	const char	*errstr;
-
-	if (argc != 4)
-		goto usage;
-
-        fid = strtonum(argv[1], 0, UINT32_MAX, &errstr);
-	if (errstr != NULL) {
-		log_warnx("fid is %s: %s", errstr, argv[1]);
-		return;
-	}
-
-	tattach(fid, NOFID, argv[2], argv[3]);
-	do_send();
-	return;
-
-usage:
-	log_warnx("usage: attach fid uname aname");
-}
-
-/* clunk fid */
-static void
-excmd_clunk(const char **argv, int argc)
-{
-	uint32_t	 fid;
-	const char	*errstr;
-
-	if (argc != 2)
-		goto usage;
-
-	fid = strtonum(argv[1], 0, UINT32_MAX, &errstr);
-	if (errstr != NULL) {
-		log_warnx("fid is %s: %s", errstr, argv[1]);
-		return;
-	}
-
-	tclunk(fid);
-	do_send();
-	return;
-
-usage:
-	log_warnx("usage: clunk fid");
-}
-
-/* flush oldtag */
-static void
-excmd_flush(const char **argv, int argc)
-{
-	uint16_t	 oldtag;
-	const char	*errstr;
-
-	if (argc != 2)
-		goto usage;
-
-	oldtag = strtonum(argv[1], 0, UINT16_MAX, &errstr);
-	if (errstr != NULL) {
-		log_warnx("oldtag is %s: %s", errstr, argv[1]);
-		return;
-	}
-
-	tflush(oldtag);
-	do_send();
-	return;
-
-usage:
-	log_warnx("usage: flush oldtag");
-}
-
-/* walk fid newfid wnames... */
-static void
-excmd_walk(const char **argv, int argc)
-{
-	uint32_t	 fid, newfid;
-	const char	*errstr;
-
-	if (argc < 3)
-		goto usage;
-
-	fid = strtonum(argv[1], 0, UINT32_MAX, &errstr);
-	if (errstr != NULL) {
-		log_warnx("fid is %s: %s", errstr, argv[1]);
-		return;
-	}
-
-	newfid = strtonum(argv[2], 0, UINT32_MAX, &errstr);
-	if (errstr != NULL) {
-		log_warnx("newfid is %s: %s", errstr, argv[1]);
-		return;
-	}
-
-	twalk(fid, newfid, argv + 3, argc - 3);
-	do_send();
-	return;
-
-usage:
-	log_warnx("usage: walk fid newfid wnames...");
-}
-
-/* open fid mode [flag] */
-static void
-excmd_open(const char **argv, int argc)
-{
-	const char	*errstr;
-	uint32_t	 fid;
-	uint8_t		 mode = 0;
-
-	if (argc != 3 && argc != 4)
-		goto usage;
-
-	fid = strtonum(argv[1], 0, UINT32_MAX, &errstr);
-	if (errstr != NULL) {
-		log_warnx("fid is %s: %s", errstr, argv[1]);
-		return;
-	}
-
-	/* parse mode */
-	if (!strcmp("read", argv[2]) || !strcmp("r", argv[2]))
-		mode = KOREAD;
-	else if (!strcmp("write", argv[2]) || !strcmp("w", argv[2]))
-		mode = KOWRITE;
-	else if (!strcmp("readwrite", argv[2]) || !strcmp("rw", argv[2]))
-		mode = KORDWR;
-	else {
-		log_warnx("invalid mode %s", argv[2]);
-		return;
-	}
-
-	/* parse flag */
-	if (argv[3] != NULL) {
-		if (!strcmp("trunc", argv[3]))
-			mode |= KOTRUNC;
-		else if (!strcmp("rclose", argv[3]))
-			mode |= KORCLOSE;
-		else {
-			log_warnx("invalid flag %s", argv[3]);
-			return;
-		}
-	}
-
-	topen(fid, mode);
-	do_send();
-	return;
-
-usage:
-	log_warnx("usage: open fid mode [flag]");
-}
-
-/* create fid path perm mode */
-static void
-excmd_create(const char **argv, int argc)
-{
-	const char	*errstr;
-	uint32_t	 fid;
-	uint8_t		 mode = 0;
-
-	if (argc != 5)
-		goto usage;
-
-	fid = strtonum(argv[1], 0, UINT32_MAX, &errstr);
-	if (errstr != NULL) {
-		log_warnx("fid is %s: %s", errstr, argv[1]);
-		return;
-	}
-
-	/* parse mode */
-	if (!strcmp("write", argv[4]) || !strcmp("w", argv[4]))
-		mode = KOWRITE;
-	else if (!strcmp("readwrite", argv[4]) || !strcmp("rw", argv[4]))
-		mode = KORDWR;
-	else {	    
-		log_warnx("invalid mode %s for create", argv[4]);
-		return;
-	}
-
-	tcreate(fid, argv[2], 0, mode);
-	do_send();
-	return;
-
-usage:
-	log_warnx("usage: create fid path perm mode ; perm is unused");
-}
-
-
-/* read fid offset count */
-static void
-excmd_read(const char **argv, int argc)
-{
-	uint64_t	 off;
-	uint32_t	 fid, count;
-	const char	*errstr;
-
-	if (argc != 4)
-		goto usage;
-
-	fid = strtonum(argv[1], 0, UINT32_MAX, &errstr);
-	if (errstr != NULL) {
-		log_warnx("fid is %s: %s", errstr, argv[1]);
-		return;
-	}
-
-	/* should really be UNT64_MAX but it fails... */
-	off = strtonum(argv[2], -1, UINT32_MAX, &errstr);
-	if (errstr != NULL) {
-		log_warnx("offset is %s: %s", errstr, argv[2]);
-		return;
-	}
-
-	count = strtonum(argv[3], 0, UINT32_MAX, &errstr);
-	if (errstr != NULL) {
-		log_warnx("count is %s: %s", errstr, argv[3]);
-		return;
-	}
-
-	tread(fid, off, count);
-	do_send();
-	return;
-
-usage:
-	log_warnx("usage: read fid offset count");
-}
-
-/* write fid offset content */
-static void
-excmd_write(const char **argv, int argc)
-{
-	uint64_t	 off;
-	uint32_t	 fid, count;
-	const char	*errstr;
-
-	if (argc != 4)
-		goto usage;
-
-	fid = strtonum(argv[1], 0, UINT32_MAX, &errstr);
-	if (errstr != NULL) {
-		log_warnx("fid is %s: %s", errstr, argv[1]);
-		return;
-	}
-
-	/* should really be UINT64_MAX but... */
-	off = strtonum(argv[2], 0, UINT32_MAX, &errstr);
-	if (errstr != NULL) {
-		log_warnx("offset is %s: %s", errstr, argv[2]);
-		return;
-	}
-
-	count = strlen(argv[3]);
-	twrite(fid, off, argv[3], count);
-	do_send();
-	return;
-
-usage:
-	log_warnx("usage: write fid offset content");
-}
-
-/* remove fid */
-static void
-excmd_remove(const char **argv, int argc)
-{
-	const char	*errstr;
-	uint32_t	 fid;
-
-	if (argc != 2)
-		goto usage;
-
-	fid = strtonum(argv[1], 0, UINT32_MAX, &errstr);
-	if (errstr != NULL) {
-		log_warnx("fid is %s: %s", errstr, argv[1]);
-		return;
-	}
-
-	tremove(fid);
-	do_send();
-	return;
-
-usage:
-	log_warnx("usage: remove fid");
-}
-
-static void
-excmd(const char **argv, int argc)
-{
-	struct cmd {
-		const char	*name;
-		void		(*fn)(const char **, int);
-	} cmds[] = {
-		{"version",	excmd_version},
-		{"attach",	excmd_attach},
-		{"clunk",	excmd_clunk},
-		{"flush",	excmd_flush},
-		{"walk",	excmd_walk},
-		{"open",	excmd_open},
-		{"create",	excmd_create},
-		{"read",	excmd_read},
-		{"write",	excmd_write},
-		/* TODO: stat */
-		{"remove",	excmd_remove},
-	};
-	size_t i;
-
-	if (argc == 0)
-		return;
-
-	for (i = 0; i < sizeof(cmds)/sizeof(cmds[0]); ++i) {
-		if (!strcmp(cmds[i].name, argv[0])) {
-			cmds[i].fn(argv, argc);
-			return;
-		}
-	}
-
-	log_warnx("Unknown command %s", *argv);
-}
-
-static void
-pp_qid(const uint8_t *d, uint32_t len)
-{
-	uint64_t	path;
-	uint32_t	vers;
-	uint8_t		type;
-
-	if (len < 13) {
-		printf("invalid");
-		return;
-	}
-
-	type = *d++;
-
-	memcpy(&vers, d, sizeof(vers));
-	d += sizeof(vers);
-	vers = le64toh(vers);
-
-	memcpy(&path, d, sizeof(path));
-	d += sizeof(path);
-	path = le64toh(path);
-
-	printf("qid{path=%"PRIu64" version=%"PRIu32" type=0x%x\"%s\"}",
-	    path, vers, type, pp_qid_type(type));
-}
-
-static void
-pp_msg(uint32_t len, uint8_t type, uint16_t tag, const uint8_t *d)
-{
-	uint32_t	 msize, iounit, count;
-	uint16_t	 slen;
-	char		*v;
-
-	printf("len=%"PRIu32" type=%d[%s] tag=0x%x[%d] ", len,
-	    type, pp_msg_type(type), tag, tag);
-
-	len -= HEADERSIZE;
-
-	switch (type) {
-	case Rversion:
-		if (len < 6) {
-			printf("invalid: not enough space for msize "
-			    "and version provided.");
-			break;
-		}
-
-		memcpy(&msize, d, sizeof(msize));
-		d += sizeof(msize);
-		len -= sizeof(msize);
-		msize = le32toh(msize);
-
-		memcpy(&slen, d, sizeof(slen));
-		d += sizeof(slen);
-		len -= sizeof(slen);
-		slen = le16toh(slen);
-
-		if (len != slen) {
-			printf("invalid: version string length doesn't "
-			    "match.  Got %d; want %d", slen, len);
-			break;
-		}
-
-		printf("msize=%"PRIu32" version[%"PRIu16"]=\"",
-		    msize, slen);
-		fwrite(d, 1, slen, stdout);
-		printf("\"");
-
-		break;
-
-	case Rattach:
-		pp_qid(d, len);
-		break;
-
-	case Rclunk:
-	case Rflush:
-	case Rremove:
-		if (len != 0)
-			printf("invalid %s: %"PRIu32" extra bytes", 
-			    pp_msg_type(type), len);
-		break;
-
-	case Rwalk:
-		if (len < 2) {
-			printf("invaild Rwalk: less than two bytes (%d)",
-			    (int)len);
-			break;
-		}
-
-		memcpy(&slen, d, sizeof(slen));
-		d += sizeof(slen);
-		len -= sizeof(slen);
-		slen = le16toh(slen);
-
-		if (len != QIDSIZE * slen) {
-			printf("invalid Rwalk: wanted %d bytes for %d qids "
-			    "but got %"PRIu32" bytes instead",
-			    QIDSIZE*slen, slen, len);
-			break;
-		}
-
-		printf("nwqid=%"PRIu16, slen);
-
-		for (; slen != 0; slen--) {
-			printf(" ");
-			pp_qid(d, len);
-			d += QIDSIZE;
-			len -= QIDSIZE;
-		}
-
-		break;
-
-	case Ropen:
-	case Rcreate:
-		if (len != QIDSIZE + 4) {
-			printf("invalid %s: expected %d bytes; "
-			    "got %u\n", pp_msg_type(type), QIDSIZE + 4, len);
-			break;
-		}
-
-		pp_qid(d, len);
-		d += QIDSIZE;
-		len -= QIDSIZE;
-
-		memcpy(&iounit, d, sizeof(iounit));
-		d += sizeof(iounit);
-		len -= sizeof(iounit);
-		iounit = le32toh(iounit);
-		printf(" iounit=%"PRIu32, iounit);
-		break;
-
-	case Rread:
-		if (len < sizeof(count)) {
-			printf("invalid Rread: expected %zu bytes at least; "
-			    "got %u\n", sizeof(count), len);
-			break;
-		}
-
-		memcpy(&count, d, sizeof(count));
-		d += sizeof(count);
-		len -= sizeof(count);
-		count = le32toh(count);
-
-		if (len != count) {
-			printf("invalid Rread: expected %d data bytes; "
-			    "got %u\n", count, len);
-			break;
-		}
-
-		/* allocates three extra bytes, oh well... */
-		if ((v = calloc(count + 1, 4)) == NULL)
-			fatal("calloc");
-		strvisx(v, d, count, VIS_SAFE | VIS_TAB | VIS_NL | VIS_CSTYLE);
-		printf("data=%s", v);
-		free(v);
-
-		break;
-
-	case Rwrite:
-		if (len != sizeof(count)) {
-			printf("invalid Rwrite: expected %zu data bytes; "
-			    "got %u\n", sizeof(count), len);
-			break;
-		}
-
-		memcpy(&count, d, sizeof(count));
-		d += sizeof(count);
-		len -= sizeof(count);
-		count = le32toh(count);
-
-		printf("count=%d", count);
-		break;
-
-	case Rerror:
-		memcpy(&slen, d, sizeof(slen));
-		d += sizeof(slen);
-		len -= sizeof(slen);
-		slen = le16toh(slen);
-
-		if (slen != len) {
-			printf("invalid: error string length doesn't "
-			    "match.  Got %d; want %d", slen, len);
-			break;
-		}
-
-		printf("error=\"");
-		fwrite(d, 1, slen, stdout);
-		printf("\"");
-
-		break;
-
-	default:
-		if ((v = calloc(len + 1, 4)) == NULL)
-			fatal("calloc");
-		strvisx(v, d, len, VIS_SAFE | VIS_TAB | VIS_NL | VIS_CSTYLE);
-		printf("body=%s", v);
-		free(v);
-	}
-
-	printf("\n");
-}
-
-static void
-handle_9p(const uint8_t *data, size_t size)
-{
-        uint32_t len;
-	uint16_t tag;
-	uint8_t type;
-
-	assert(size >= HEADERSIZE);
-
-	memcpy(&len, data, sizeof(len));
-	data += sizeof(len);
-
-	memcpy(&type, data, sizeof(type));
-	data += sizeof(type);
-
-	memcpy(&tag, data, sizeof(tag));
-	data += sizeof(tag);
-
-	len = le32toh(len);
-	/* type is one byte long, no endianness issues */
-	tag = le16toh(tag);
-
-	clr();
-	pp_msg(len, type, tag, data);
-	prompt();
-}
-
-static void
-clr(void)
-{
-	printf("\r");
-	fflush(stdout);
-}
-
-static void
-prompt(void)
-{
-	printf("%s", PROMPT);
-	fflush(stdout);
-}
-
-int
-main(int argc, char **argv)
-{
-	int		 ch, sock, handshake;
-	struct event	 ev_sigint, ev_sigterm;
-
-	signal(SIGPIPE, SIG_IGN);
-
-	while ((ch = getopt(argc, argv, "C:cH:hK:P:v")) != -1) {
-		switch (ch) {
-		case 'C':
-			crtpath = optarg;
-			break;
-		case 'c':
-			tls = 1;
-			break;
-		case 'H':
-			host = optarg;
-			break;
-		case 'h':
-			usage(0);
-			break;
-		case 'K':
-			keypath = optarg;
-			break;
-		case 'P':
-			port = optarg;
-			break;
-		case 'v':
-			verbose = 1;
-			break;
-		default:
-			usage(1);
-		}
-	}
-
-	if (host == NULL)
-		host = "localhost";
-	if (port == NULL)
-		port = "1337";
-
-	argc -= optind;
-	argv += optind;
-
-	if (argc != 0)
-		usage(1);
-	/* if (!tls || (crtpath != NULL || keypath != NULL)) */
-		/* usage(1); */
-	if (!tls)
-                errx(1, "must enable tls (for now)");
-
-	log_init(1, LOG_DAEMON);
-	log_setverbose(verbose);
-	log_procinit(getprogname());
-
-	if ((tlsconf = tls_config_new()) == NULL)
-		fatalx("tls_config_new");
-	tls_config_insecure_noverifycert(tlsconf);
-	tls_config_insecure_noverifyname(tlsconf);
-	if (tls_config_set_keypair_file(tlsconf, crtpath, keypath) == -1)
-		fatalx("can't load certs (%s, %s)", crtpath, keypath);
-
-	if ((ctx = tls_client()) == NULL)
-		fatal("tls_client");
-	if (tls_configure(ctx, tlsconf) == -1)
-		fatalx("tls_configure: %s", tls_error(ctx));
-
-	log_info("connecting to %s:%s...", host, port);
-
-	if ((sock = openconn()) == -1)
-		fatalx("can't connect to %s:%s", host, port);
-
-	if (tls_connect_socket(ctx, sock, host) == -1)
-		fatalx("tls_connect_socket: %s", tls_error(ctx));
-
-	for (handshake = 0; !handshake;) {
-		switch (tls_handshake(ctx)) {
-		case -1:
-			fatalx("tls_handshake: %s", tls_error(ctx));
-		case 0:
-			handshake = 1;
-			break;
-		}
-	}
-
-	log_info("connected!");
-
-	mark_nonblock(sock);
-
-	event_init();
-
-	/* initialize global evb */
-	if ((evb = evbuffer_new()) == NULL)
-		fatal("evbuffer_new");
-
-	signal_set(&ev_sigint, SIGINT, sig_handler, NULL);
-	signal_set(&ev_sigterm, SIGINT, sig_handler, NULL);
-
-	signal_add(&ev_sigint, NULL);
-	signal_add(&ev_sigterm, NULL);
-
-	bev = bufferevent_new(sock, client_read, client_write, client_error,
-	    NULL);
-	if (bev == NULL)
-		fatal("bufferevent_new");
-
-	/* setup tls/io */
-	event_set(&bev->ev_read, sock, EV_READ, tls_readcb, bev);
-	event_set(&bev->ev_write, sock, EV_WRITE, tls_writecb, bev);
-
-	bufferevent_enable(bev, EV_READ|EV_WRITE);
-
-	mark_nonblock(0);
-	inbev = bufferevent_new(0, repl_read, NULL, repl_error, NULL);
-	bufferevent_enable(inbev, EV_READ);
-
-	prompt();
-	event_dispatch();
-
-	bufferevent_free(bev);
-	tls_free(ctx);
-	tls_config_free(tlsconf);
-	close(sock);
-
-	return 0;
-}
blob - 196a2a9f84052ad4fef72faea5dc79a91530ccb7 (mode 644)
blob + /dev/null
--- listener.c
+++ /dev/null
@@ -1,933 +0,0 @@
-/*
- * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
- * Copyright (c) 2018 Florian Obser <florian@openbsd.org>
- * Copyright (c) 2004, 2005 Claudio Jeker <claudio@openbsd.org>
- * Copyright (c) 2004 Esben Norby <norby@openbsd.org>
- * 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 "compat.h"
-
-#include <sys/socket.h>
-
-#include <endian.h>
-#include <errno.h>
-#include <inttypes.h>
-#include <pwd.h>
-#include <signal.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <syslog.h>
-#include <unistd.h>
-
-#include "control.h"
-#include "kamid.h"
-#include "listener.h"
-#include "log.h"
-#include "sandbox.h"
-#include "utils.h"
-
-static struct kd_conf	*listener_conf;
-static struct imsgev	*iev_main;
-
-static void	listener_sig_handler(int, short, void *);
-__dead void	listener_shutdown(void);
-
-SPLAY_HEAD(clients_tree_id, client) clients;
-
-struct client {
-	uint32_t		 id;
-	uint32_t		 lid;
-	uint32_t		 msize;
-	int			 fd;
-	int			 done;
-	struct tls		*ctx;
-	struct event		 event;
-	struct imsgev		 iev;
-	struct bufferevent	*bev;
-	SPLAY_ENTRY(client)	 sp_entry;
-};
-
-static void	listener_imsg_event_add(struct imsgev *, void *);
-static void	listener_dispatch_client(int, short, void *);
-static int	listener_imsg_compose_client(struct client *, int,
-    uint32_t, const void *, uint16_t);
-
-static void	apply_config(struct kd_conf *);
-static void	handle_accept(int, short, void *);
-
-static void	handle_handshake(int, short, void *);
-static void	client_read(struct bufferevent *, void *);
-static void	client_write(struct bufferevent *, void *);
-static void	client_error(struct bufferevent *, short, void *);
-static void	client_tls_readcb(int, short, void *);
-static void	client_tls_writecb(int, short, void *);
-static void	close_conn(struct client *);
-static void	handle_close(int, short, void *);
-
-static inline int
-clients_tree_cmp(struct client *a, struct client *b)
-{
-	if (a->id == b->id)
-		return 0;
-	else if (a->id < b->id)
-		return -1;
-	else
-		return +1;
-}
-
-SPLAY_PROTOTYPE(clients_tree_id, client, sp_entry, clients_tree_cmp);
-SPLAY_GENERATE(clients_tree_id, client, sp_entry, clients_tree_cmp)
-
-static void
-listener_sig_handler(int sig, short event, void *d)
-{
-	/*
-	 * Normal signal handler rules don't apply because libevent
-	 * decouples for us.
-	 */
-
-	switch (sig) {
-	case SIGINT:
-	case SIGTERM:
-		listener_shutdown();
-	default:
-		fatalx("unexpected signal %d", sig);
-	}
-}
-
-void
-listener(int debug, int verbose)
-{
-	struct event		 ev_sigint, ev_sigterm;
-	struct passwd		*pw;
-
-	/* listener_conf = config_new_empty(); */
-
-	log_init(debug, LOG_DAEMON);
-	log_setverbose(verbose);
-
-	if ((pw = getpwnam(KD_USER)) == NULL)
-		fatal("getpwnam");
-
-	if (chroot(pw->pw_dir) == -1)
-		fatal("chroot");
-	if (chdir("/") == -1)
-		fatal("chdir(\"/\")");
-
-	setproctitle("listener");
-	log_procinit("listener");
-
-	if (setgroups(1, &pw->pw_gid) ||
-	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
-	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
-		fatal("can't drop privileges");
-
-	event_init();
-
-	/* Setup signal handlers(s). */
-	signal_set(&ev_sigint, SIGINT, listener_sig_handler, NULL);
-	signal_set(&ev_sigterm, SIGTERM, listener_sig_handler, NULL);
-
-	signal_add(&ev_sigint, NULL);
-	signal_add(&ev_sigterm, NULL);
-
-	signal(SIGPIPE, SIG_IGN);
-	signal(SIGHUP, SIG_IGN);
-
-	/* Setup pipe and event handler to the main process. */
-	if ((iev_main = malloc(sizeof(*iev_main))) == NULL)
-		fatal(NULL);
-
-	imsg_init(&iev_main->ibuf, 3);
-	iev_main->handler = listener_dispatch_main;
-
-	/* Setup event handlers. */
-	iev_main->events = EV_READ;
-	event_set(&iev_main->ev, iev_main->ibuf.fd, iev_main->events,
-	    iev_main->handler, iev_main);
-	event_add(&iev_main->ev, NULL);
-
-	sandbox_listener();
-	event_dispatch();
-	listener_shutdown();
-}
-
-__dead void
-listener_shutdown(void)
-{
-	msgbuf_clear(&iev_main->ibuf.w);
-	close(iev_main->ibuf.fd);
-
-	config_clear(listener_conf);
-
-	free(iev_main);
-
-	log_info("listener exiting");
-	exit(0);
-}
-
-static void
-listener_receive_config(struct imsg *imsg, struct kd_conf **nconf,
-    struct kd_pki_conf **pki)
-{
-	struct kd_listen_conf *listen;
-	char *t;
-
-	switch (imsg->hdr.type) {
-	case IMSG_RECONF_CONF:
-		if (*nconf != NULL)
-			fatalx("%s: IMSG_RECONF_CONF already in "
-			    "progress", __func__);
-
-		if (listener_conf != NULL)
-			fatalx("%s: don't know how reload the "
-			    "configuration yet", __func__);
-
-		if (IMSG_DATA_SIZE(*imsg) != sizeof(struct kd_conf))
-			fatalx("%s: IMSG_RECONF_CONF wrong length: %lu",
-			    __func__, IMSG_DATA_SIZE(*imsg));
-		if ((*nconf = malloc(sizeof(**nconf))) == NULL)
-			fatal(NULL);
-		memcpy(*nconf, imsg->data, sizeof(**nconf));
-		memset(&(*nconf)->pki_head, 0, sizeof((*nconf)->pki_head));
-		memset(&(*nconf)->table_head, 0, sizeof((*nconf)->table_head));
-		memset(&(*nconf)->listen_head, 0, sizeof((*nconf)->listen_head));
-		break;
-	case IMSG_RECONF_PKI:
-		if (*nconf == NULL)
-			fatalx("%s: IMSG_RECONF_PKI without "
-			    "IMSG_RECONF_CONF", __func__);
-		*pki = xcalloc(1, sizeof(**pki));
-		t = imsg->data;
-		t[IMSG_DATA_SIZE(*imsg)-1] = '\0';
-		strlcpy((*pki)->name, t, sizeof((*pki)->name));
-		break;
-	case IMSG_RECONF_PKI_CERT:
-		if (*pki == NULL)
-			fatalx("%s: IMSG_RECONF_PKI_CERT without "
-			    "IMSG_RECONF_PKI", __func__);
-		(*pki)->certlen = IMSG_DATA_SIZE(*imsg);
-		(*pki)->cert = xmemdup(imsg->data, (*pki)->certlen);
-		break;
-	case IMSG_RECONF_PKI_KEY:
-		if (*pki == NULL)
-			fatalx("%s: IMSG_RECONF_PKI_KEY without "
-			    "IMSG_RECONF_PKI", __func__);
-		(*pki)->keylen = IMSG_DATA_SIZE(*imsg);
-		(*pki)->key = xmemdup(imsg->data, (*pki)->keylen);
-		STAILQ_INSERT_HEAD(&(*nconf)->pki_head, *pki, entry);
-		pki = NULL;
-		break;
-	case IMSG_RECONF_LISTEN:
-		if (*nconf == NULL)
-			fatalx("%s: IMSG_RECONF_LISTEN without "
-			    "IMSG_RECONF_CONF", __func__);
-		if (IMSG_DATA_SIZE(*imsg) != sizeof(*listen))
-			fatalx("%s: IMSG_RECONF_LISTEN wrong length: %lu",
-			    __func__, IMSG_DATA_SIZE(*imsg));
-		listen = xcalloc(1, sizeof(*listen));
-		memcpy(listen, imsg->data, sizeof(*listen));
-		memset(&listen->entry, 0, sizeof(listen->entry));
-		if ((listen->fd = imsg->fd) == -1)
-			fatalx("%s: IMSG_RECONF_LISTEN no fd",
-			    __func__);
-		listen->auth_table = NULL;
-		memset(&listen->ev, 0, sizeof(listen->ev));
-		STAILQ_INSERT_HEAD(&(*nconf)->listen_head, listen, entry);
-		break;
-	case IMSG_RECONF_END:
-		if (*nconf == NULL)
-			fatalx("%s: IMSG_RECONF_END without "
-			    "IMSG_RECONF_CONF", __func__);
-		/* merge_config(listener_conf, nconf); */
-		apply_config(*nconf);
-		*nconf = NULL;
-		break;
-	}
-}
-
-static inline struct kd_listen_conf *
-listen_by_id(uint32_t id)
-{
-	struct kd_listen_conf *l;
-
-	STAILQ_FOREACH(l, &listener_conf->listen_head, entry) {
-		if (l->id == id)
-			return l;
-	}
-	return NULL;
-}
-
-void
-listener_dispatch_main(int fd, short event, void *d)
-{
-	static struct kd_conf		*nconf;
-	static struct kd_pki_conf	*pki;
-	struct kd_listen_conf		*listen;
-	struct client			*client, find;
-	struct imsg			 imsg;
-	struct imsgev			*iev = d;
-	struct imsgbuf			*ibuf;
-	ssize_t				 n;
-	int				 shut = 0;
-
-	ibuf = &iev->ibuf;
-
-	if (event & EV_READ) {
-		if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
-			fatal("imsg_read error");
-		if (n == 0)	/* Connection closed. */
-			shut = 1;
-	}
-	if (event & EV_WRITE) {
-		if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
-			fatal("msgbuf_write");
-		if (n == 0)	/* Connection closed. */
-			shut = 1;
-	}
-
-	for (;;) {
-		if ((n = imsg_get(ibuf, &imsg)) == -1)
-			fatal("%s: imsg_get error", __func__);
-		if (n == 0)	/* No more messages. */
-			break;
-
-		switch (imsg.hdr.type) {
-		case IMSG_CONTROLFD:
-			if ((fd = imsg.fd) == -1)
-				fatalx("%s: expected to receive imsg "
-				    "control fd but didn't receive any",
-				    __func__);
-			/* Listen on control socket. */
-			control_listen(fd);
-			break;
-		case IMSG_RECONF_CONF:
-		case IMSG_RECONF_PKI:
-		case IMSG_RECONF_PKI_CERT:
-		case IMSG_RECONF_PKI_KEY:
-		case IMSG_RECONF_LISTEN:
-		case IMSG_RECONF_END:
-			listener_receive_config(&imsg, &nconf, &pki);
-			break;
-		case IMSG_AUTH:
-			find.id = imsg.hdr.peerid;
-			client = SPLAY_FIND(clients_tree_id, &clients, &find);
-			if (client == NULL) {
-				if (imsg.fd != -1)
-					close(imsg.fd);
-				break;
-			}
-			if (imsg.fd == -1) {
-				log_info("got fd = -1, auth failed?");
-				close_conn(client);
-				break;
-			}
-			imsg_init(&client->iev.ibuf, imsg.fd);
-			client->iev.events = EV_READ;
-			client->iev.handler = listener_dispatch_client;
-			event_set(&client->iev.ev, client->iev.ibuf.fd,
-			    client->iev.events, client->iev.handler, client);
-			listener_imsg_compose_client(client, IMSG_AUTH,
-			    client->id, imsg.data, IMSG_DATA_SIZE(imsg));
-			break;
-		case IMSG_AUTH_DIR:
-			find.id = imsg.hdr.peerid;
-			client = SPLAY_FIND(clients_tree_id, &clients, &find);
-			if (client == NULL) {
-				log_info("got AUTH_DIR but client gone");
-				break;
-			}
-
-			listener_imsg_compose_client(client, IMSG_AUTH_DIR,
-			    0, imsg.data, IMSG_DATA_SIZE(imsg));
-
-			client->bev = bufferevent_new(client->fd,
-			    client_read, client_write, client_error,
-			    client);
-			if (client->bev == NULL) {
-				log_info("failed to allocate client buffer");
-				close_conn(client);
-				return;
-			}
-
-#if HAVE_EVENT2
-			evbuffer_unfreeze(client->bev->input, 0);
-			evbuffer_unfreeze(client->bev->output, 1);
-#endif
-
-			listen = listen_by_id(client->lid);
-			if (listen->flags & L_TLS) {
-				event_set(&client->bev->ev_read, client->fd,
-				    EV_READ, client_tls_readcb, client->bev);
-				event_set(&client->bev->ev_write, client->fd,
-				    EV_WRITE, client_tls_writecb, client->bev);
-			}
-
-			/*
-			 * Read or write at least a header before
-			 * firing the callbacks.  High watermark of 0
-			 * to never stop reading/writing; probably to
-			 * be revisited.
-			 */
-			/* bufferevent_setwatermark(client->bev, EV_READ|EV_WRITE, */
-			    /* sizeof(struct np_msg_header), 0); */
-			bufferevent_enable(client->bev, EV_READ|EV_WRITE);
-			break;
-
-		default:
-			log_debug("%s: unexpected imsg %d", __func__,
-			    imsg.hdr.type);
-			break;
-		}
-		imsg_free(&imsg);
-	}
-
-	if (!shut)
-		listener_imsg_event_add(iev, d);
-	else {
-		/* This pipe is dead.  Remove its event handler. */
-		event_del(&iev->ev);
-		log_warnx("pipe closed, shutting down...");
-		event_loopexit(NULL);
-	}
-}
-
-int
-listener_imsg_compose_main(int type, uint32_t peerid, const void *data,
-    uint16_t datalen)
-{
-	return imsg_compose_event(iev_main, type, peerid, 0, -1, data,
-	    datalen);
-}
-
-static void
-listener_imsg_event_add(struct imsgev *iev, void *d)
-{
-	iev->events = EV_READ;
-	if (iev->ibuf.w.queued)
-		iev->events |= EV_WRITE;
-
-	event_del(&iev->ev);
-	event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, d);
-	event_add(&iev->ev, NULL);
-}
-
-static void
-listener_dispatch_client(int fd, short event, void *d)
-{
-        struct client	 find, *client = d;
-	struct imsg	 imsg;
-	struct imsgev	*iev;
-	struct imsgbuf	*ibuf;
-	ssize_t		 n;
-	int		 r, shut = 0;
-
-	iev = &client->iev;
-	ibuf = &iev->ibuf;
-
-	if (event & EV_READ) {
-		if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
-			fatal("imsg_read error");
-		if (n == 0)	/* Connection closed */
-			shut = 1;
-	}
-
-	if (event & EV_WRITE) {
-		if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
-			fatal("msgbuf_write");
-		if (n == 0)	/* Connection closed. */
-			shut = 1;
-	}
-
-	for (;;) {
-		if ((n = imsg_get(ibuf, &imsg)) == -1)
-			fatal("%s: imsg_get error", __func__);
-		if (n == 0)	/* No more messages. */
-			break;
-
-		switch (imsg.hdr.type) {
-		case IMSG_BUF:
-			find.id = imsg.hdr.peerid;
-			client = SPLAY_FIND(clients_tree_id, &clients, &find);
-			if (client == NULL) {
-				log_info("got IMSG_BUF but client (%d) gone",
-				    imsg.hdr.peerid);
-				break;
-			}
-			r = bufferevent_write(client->bev, imsg.data,
-			    IMSG_DATA_SIZE(imsg));
-			if (r == -1) {
-				log_warn("%s: bufferevent_write failed",
-				    __func__);
-				close_conn(client);
-				break;
-			}
-			break;
-
-		case IMSG_MSIZE:
-			if (IMSG_DATA_SIZE(imsg) != sizeof(client->msize))
-				fatal("IMSG_MSIZE size mismatch: "
-				    "got %zu want %zu", IMSG_DATA_SIZE(imsg),
-				    sizeof(client->msize));
-
-			memcpy(&client->msize, imsg.data,
-			    sizeof(client->msize));
-
-			if (client->msize == 0)
-				fatal("IMSG_MSIZE got msize = 0");
-
-			break;
-
-		case IMSG_CLOSE:
-			/*
-			 * Both EVBUFFER_READ or EVBUFFER_WRITE should
-			 * be fine.
-			 */
-			client_error(client->bev, EVBUFFER_READ, client);
-			break;
-
-		default:
-			log_debug("%s: unexpected imsg %d", __func__,
-			    imsg.hdr.type);
-			break;
-		}
-		imsg_free(&imsg);
-	}
-
-	if (!shut)
-		listener_imsg_event_add(iev, d);
-	else {
-		/* This pipe is dead.  Remove its handler */
-		log_debug("client proc vanished");
-                close_conn(client);
-	}
-}
-
-static int
-listener_imsg_compose_client(struct client *client, int type,
-    uint32_t peerid, const void *data, uint16_t len)
-{
-	int ret;
-
-	if ((ret = imsg_compose(&client->iev.ibuf, type, peerid, 0, -1,
-	    data, len)) != -1)
-		listener_imsg_event_add(&client->iev, client);
-
-	return ret;
-}
-
-static inline struct kd_pki_conf *
-pki_by_name(const char *name)
-{
-        struct kd_pki_conf *pki;
-
-	STAILQ_FOREACH(pki, &listener_conf->pki_head, entry) {
-		if (!strcmp(name, pki->name))
-			return pki;
-	}
-
-	return NULL;
-}
-
-static void
-apply_config(struct kd_conf *conf)
-{
-	struct kd_pki_conf *pki;
-	struct kd_listen_conf *listen;
-
-	listener_conf = conf;
-
-	/* prepare the various tls_config */
-	STAILQ_FOREACH(pki, &listener_conf->pki_head, entry) {
-		if ((pki->tlsconf = tls_config_new()) == NULL)
-			fatal("tls_config_new");
-		tls_config_verify_client_optional(pki->tlsconf);
-		tls_config_insecure_noverifycert(pki->tlsconf);
-		if (tls_config_set_keypair_mem(pki->tlsconf,
-		    pki->cert, pki->certlen,
-		    pki->key, pki->keylen) == -1)
-			fatalx("tls_config_set_keypair_mem: %s",
-			    tls_config_error(pki->tlsconf));
-	}
-
-	/* prepare and kickoff the listeners */
-	STAILQ_FOREACH(listen, &listener_conf->listen_head, entry) {
-		if ((listen->ctx = tls_server()) == NULL)
-			fatal("tls_server");
-
-		pki = pki_by_name(listen->pki);
-		if (tls_configure(listen->ctx, pki->tlsconf) == -1)
-			fatalx("tls_configure: %s",
-			    tls_config_error(pki->tlsconf));
-
-		event_set(&listen->ev, listen->fd, EV_READ|EV_PERSIST,
-		    handle_accept, listen);
-		event_add(&listen->ev, NULL);
-	}
-}
-
-static inline void
-yield_r(struct client *c, void (*fn)(int, short, void *))
-{
-	if (event_pending(&c->event, EV_WRITE|EV_READ, NULL))
-		event_del(&c->event);
-	event_set(&c->event, c->fd, EV_READ, fn, c);
-	event_add(&c->event, NULL);
-}
-
-static inline void
-yield_w(struct client *c, void (*fn)(int, short, void *))
-{
-	if (event_pending(&c->event, EV_WRITE|EV_READ, NULL))
-		event_del(&c->event);
-	event_set(&c->event, c->fd, EV_WRITE, fn, c);
-	event_add(&c->event, NULL);
-}
-
-static inline uint32_t
-random_id(void)
-{
-#if HAVE_ARC4RANDOM
-# define RANDID() arc4random()
-#else
-	/* not as pretty as a random id */
-	static uint32_t counter = 0;
-# define RANDID() counter++
-#endif
-
-	struct client find, *res;
-
-	for (;;) {
-		find.id = RANDID();
-		res = SPLAY_FIND(clients_tree_id, &clients, &find);
-		if (res == NULL)
-			return find.id;
-	}
-
-#undef RANDID
-}
-
-static void
-handle_accept(int fd, short ev, void *data)
-{
-	struct kd_listen_conf *listen = data;
-	struct client *c;
-	int s;
-
-	if ((s = accept(fd, NULL, NULL)) == -1) {
-		log_warn("accept");
-		return;
-	}
-
-	c = xcalloc(1, sizeof(*c));
-	c->msize = MSIZE9P;
-	c->lid = listen->id;
-	c->iev.ibuf.fd = -1;
-
-	if (tls_accept_socket(listen->ctx, &c->ctx, s) == -1) {
-		log_warnx("tls_accept_socket: %s",
-		    tls_error(listen->ctx));
-		free(c);
-		close(s);
-		return;
-	}
-
-	c->fd = s;
-	c->id = random_id();
-
-	SPLAY_INSERT(clients_tree_id, &clients, c);
-
-	/* initialize the event */
-	event_set(&c->event, c->fd, EV_READ, NULL, NULL);
-
-	yield_r(c, handle_handshake);
-}
-
-static void
-handle_handshake(int fd, short ev, void *data)
-{
-	struct client *c = data;
-	struct kd_auth_req auth;
-	ssize_t r;
-	const char *hash;
-
-	switch (r = tls_handshake(c->ctx)) {
-	case TLS_WANT_POLLIN:
-		yield_r(c, handle_handshake);
-		return;
-	case TLS_WANT_POLLOUT:
-		yield_w(c, handle_handshake);
-		return;
-	case -1:
-		log_debug("handhsake failed: %s", tls_error(c->ctx));
-		close_conn(c);
-		return;
-	}
-
-	if ((hash = tls_peer_cert_hash(c->ctx)) == NULL) {
-		log_warnx("client didn't provide certificate");
-		close_conn(c);
-		return;
-	}
-
-	memset(&auth, 0, sizeof(auth));
-	auth.listen_id = c->lid;
-	strlcpy(auth.hash, hash, sizeof(auth.hash));
-	log_debug("sending hash %s", auth.hash);
-
-	listener_imsg_compose_main(IMSG_AUTH_TLS, c->id,
-	    &auth, sizeof(auth));
-}
-
-static void
-client_read(struct bufferevent *bev, void *d)
-{
-	struct client	*client = d;
-	struct evbuffer	*src = EVBUFFER_INPUT(bev);
-	uint32_t	 len;
-
-	for (;;) {
-		if (EVBUFFER_LENGTH(src) < 4)
-			return;
-
-		memcpy(&len, EVBUFFER_DATA(src), sizeof(len));
-		len = le32toh(len);
-		log_debug("expecting a message %"PRIu32" bytes long "
-		    "(of wich %zu already read)",
-		    len, EVBUFFER_LENGTH(src));
-
-		if (len < HEADERSIZE) {
-			log_warnx("invalid message size %d (too low)", len);
-			client_error(bev, EVBUFFER_READ, client);
-			return;
-		}
-
-		if (len > client->msize) {
-			log_warnx("incoming message bigger than msize "
-			    "(%"PRIu32" vs %"PRIu32")", len, client->msize);
-			client_error(bev, EVBUFFER_READ, client);
-			return;
-		}
-
-		if (len > EVBUFFER_LENGTH(src))
-			return;
-
-		listener_imsg_compose_client(client, IMSG_BUF, client->id,
-		    EVBUFFER_DATA(src), len);
-		evbuffer_drain(src, len);
-	}
-}
-
-static void
-client_write(struct bufferevent *bev, void *d)
-{
-	/*
-	 * here we can do some fancy logic like deciding when to call
-	 *
-	 *	(*bev->errorcb)(bev, EVBUFFER_WRITE, bev->cbarg)
-	 *
-	 * to signal the end of the transaction.
-	 */
-
-	return;
-}
-
-static void
-client_error(struct bufferevent *bev, short err, void *d)
-{
-	struct client	*client = d;
-	struct evbuffer	*buf;
-
-        if (err & EVBUFFER_ERROR) {
-		if (errno == EFBIG) {
-			bufferevent_enable(bev, EV_READ);
-			return;
-		}
-		log_debug("buffer event error");
-                close_conn(client);
-		return;
-	}
-
-	if (err & EVBUFFER_EOF) {
-                close_conn(client);
-		return;
-	}
-
-	if (err & (EVBUFFER_READ|EVBUFFER_WRITE)) {
-		bufferevent_disable(bev, EV_READ|EV_WRITE);
-		client->done = 1;
-
-		buf = EVBUFFER_OUTPUT(client->bev);
-		if (EVBUFFER_LENGTH(buf) != 0) {
-			/* finish writing all the data first */
-			bufferevent_enable(client->bev, EV_WRITE);
-			return;
-		}
-
-		close_conn(client);
-		return;
-	}
-
-	log_warnx("unknown event error, closing client connection");
-	close_conn(client);
-}
-
-static void
-client_tls_readcb(int fd, short event, void *d)
-{
-	struct bufferevent	*bufev = d;
-	struct client		*client = bufev->cbarg;
-	char			 buf[IBUF_READ_SIZE];
-	int			 what = EVBUFFER_READ;
-	int			 howmuch = IBUF_READ_SIZE;
-	ssize_t			 ret;
-	size_t			 len;
-
-	if (event == EV_TIMEOUT) {
-		what |= EVBUFFER_TIMEOUT;
-		goto err;
-	}
-
-	if (bufev->wm_read.high != 0)
-		howmuch = MIN(sizeof(buf), bufev->wm_read.high);
-
-        switch (ret = tls_read(client->ctx, buf, howmuch)) {
-	case TLS_WANT_POLLIN:
-	case TLS_WANT_POLLOUT:
-		goto retry;
-	case -1:
-		what |= EVBUFFER_ERROR;
-		goto err;
-	}
-	len = ret;
-
-	if (len == 0) {
-		what |= EVBUFFER_EOF;
-		goto err;
-	}
-
-	if (evbuffer_add(bufev->input, buf, len) == -1) {
-		what |= EVBUFFER_ERROR;
-		goto err;
-	}
-
-	event_add(&bufev->ev_read, NULL);
-
-	len = EVBUFFER_LENGTH(bufev->input);
-	if (bufev->wm_read.low != 0 && len < bufev->wm_read.low)
-		return;
-	if (bufev->wm_read.high != 0 && len > bufev->wm_read.high) {
-		/*
-		 * here we could implement some read pressure
-		 * mechanism.
-		 */
-	}
-
-	if (bufev->readcb != NULL)
-		(*bufev->readcb)(bufev, bufev->cbarg);
-
-	return;
-
-retry:
-	event_add(&bufev->ev_read, NULL);
-	return;
-
-err:
-	(*bufev->errorcb)(bufev, what, bufev->cbarg);
-}
-
-static void
-client_tls_writecb(int fd, short event, void *d)
-{
-	struct bufferevent	*bufev = d;
-	struct client		*client = bufev->cbarg;
-	ssize_t			 ret;
-	size_t			 len;
-	short			 what = EVBUFFER_WRITE;
-
-	if (event == EV_TIMEOUT) {
-		what |= EVBUFFER_TIMEOUT;
-		goto err;
-	}
-
-	if (EVBUFFER_LENGTH(bufev->output) != 0) {
-		ret = tls_write(client->ctx,
-		    EVBUFFER_DATA(bufev->output),
-		    EVBUFFER_LENGTH(bufev->output));
-		switch (ret) {
-		case TLS_WANT_POLLIN:
-		case TLS_WANT_POLLOUT:
-			goto retry;
-		case -1:
-			what |= EVBUFFER_ERROR;
-			goto err;
-		}
-		len = ret;
-		evbuffer_drain(bufev->output, len);
-	}
-
-	if (EVBUFFER_LENGTH(bufev->output) != 0)
-		event_add(&bufev->ev_write, NULL);
-
-	if (bufev->writecb != NULL &&
-	    EVBUFFER_LENGTH(bufev->output) <= bufev->wm_write.low)
-		(*bufev->writecb)(bufev, bufev->cbarg);
-	return;
-
-retry:
-	event_add(&bufev->ev_write, NULL);
-	return;
-
-err:
-	(*bufev->errorcb)(bufev, what, bufev->cbarg);
-}
-
-static void
-close_conn(struct client *c)
-{
-	log_debug("closing connection");
-
-	if (c->iev.ibuf.fd != -1) {
-		listener_imsg_compose_client(c, IMSG_CONN_GONE, 0, NULL, 0);
-		imsg_flush(&c->iev.ibuf);
-		msgbuf_clear(&c->iev.ibuf.w);
-		event_del(&c->iev.ev);
-		close(c->iev.ibuf.fd);
-	}
-
-	handle_close(c->fd, 0, c);
-}
-
-static void
-handle_close(int fd, short ev, void *d)
-{
-	struct client *c = d;
-
-	switch (tls_close(c->ctx)) {
-	case TLS_WANT_POLLIN:
-		yield_r(c, handle_close);
-		return;
-	case TLS_WANT_POLLOUT:
-		yield_w(c, handle_close);
-		return;
-	}
-
-	event_del(&c->event);
-	tls_free(c->ctx);
-	close(c->fd);
-	free(c);
-}
blob - 4869c415ac1237dc481e958ab418713eb08d313b (mode 644)
blob + /dev/null
--- listener.h
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef LISTENER_H
-#define LISTENER_H
-
-#include "compat.h"
-
-void		listener(int, int);
-void		listener_dispatch_main(int, short, void *);
-int		listener_imsg_compose_main(int, uint32_t, const void *, uint16_t);
-
-#endif
blob - 859eb2b7dc29fb815517252d515596e8a2c801f3 (mode 644)
blob + /dev/null
--- log.c
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * 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 "compat.h"
-
-#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: %s\n", log_procname, fmt) == -1) {
-			fprintf(stderr, "%s: ", log_procname);
-			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 - 06d92fcce2b042403dc0d18cc578cf59e8fbc9d8 (mode 644)
blob + /dev/null
--- log.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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 "compat.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 - 6da049b0758a48894158408f942e91823baa3ade (mode 644)
blob + /dev/null
--- ninepscript.5
+++ /dev/null
@@ -1,265 +0,0 @@
-.\" 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.
-.\"
-.Dd $Mdocdate: December 02 2021$
-.Dt NINEPSCRIPT 5
-.Os
-.Sh NAME
-.Nm ninepscript
-.Nd kamid regress test scripting language
-.Sh DESCRIPTION
-.Nm
-is a custom DSL
-.Pq domain specific language
-used to write the regression suite of
-.Xr kamid 8 .
-it has a fairly simple and regular syntax that features constant
-declarations, routines, test cases.
-It does not support conditional or loops.
-.Pp
-Additional files can be included with the
-.Ic include
-keyword, for example
-.Bd -literal -offset Ds
-include "lib.9ps"
-.Ed
-.Pp
-Comments can be placed anywhere, start with the # character and extend
-until the end of the line.
-.Pp
-An expression is a fundamental building block.
-It is something that yields a value.
-An expression may be either a:
-.Bl -tag -width variable_reference
-.It literal
-a bare number or string.
-A string is a sequence of characters enclosed in single or double quotes
-.Sq like this
-or
-.Dq like this .
-.It routine call
-Evaluate the routine code and return the value computed by it.
-The syntax is
-.Bd -literal -offset Ds
-.Ar routine Ns Po Ar arguments... Pc
-.Ed
-.Pp
-The
-.Ql ...
-special syntax expands to the list of variable arguments of the
-current routine.
-Be aware that the implementation of the variable arguments is quirky
-and has a lot of corner cases, use with care!
-.It variable reference
-a variable
-.Pq or constant
-reference is the name of a previously defined variable or constant.
-It evaluates to the value of the variable or constant in the current
-scope.
-.It comparison
-The syntax is
-.Bd -literal -offset Ds
-.Ar expression Cm == Ar expression
-.Ar expression Cm <= Ar expression
-.Ed
-.Pp
-and yields a true value if the two expressions are considered to be
-respectively
-.Sq equal
-or
-.Sq lesser equal ,
-a false value otherwise.
-Two values are equal if they are both number and represent the same
-value
-.Pq regardless of the size
-or if they're both the same string.
-.It cast
-convert one value to another type.
-The syntax is
-.Ql Ar expression : Ns Ar type
-where type is one of
-.Sq u8 ,
-.Sq u16 ,
-.Sq u32
-or
-.Sq str .
-.It field access
-Access a field of a complex object.
-The syntax is
-.Ql Ar object . Ns Ar field .
-See the
-.Sx OBJECTS AND FIELDS
-section for the description of objects types and fields allowed.
-.El
-.Pp
-An expression is considered to be
-.Dq false
-if evaluates to a number and its value is zero.
-Otherwise, it's considered to be
-.Dq true .
-.Pp
-The top-level declarations are:
-.Bl -tag -width Ds
-.It Ic const Ar identifier No = Ar value
-Declare
-.Ar identifier
-to be a constant that evaluates to
-.Ar value .
-.Ar value
-must be a literal or a cast from a literal.
-Multiple constant can be declared at the same time using the following
-syntax:
-.Bd -literal -offset Ds
-.Ic const (
-	foo = 5
-	bar = 7
-)
-.Ed
-.Pp
-Note that newlines are mandatory after an
-.Ar identifier No = Ar value
-line in this case.
-.It Ic proc Ar name Ns Po Ar arguments ... Pc Brq code ...
-Define a routine called
-.Ar name
-that accepts the comma-separated list of
-.Ar arguments .
-When a routine is called, its
-.Ar code
-gets evaluated in a lexical scope where
-.Ar arguments
-are defined to the value passed by the caller.
-A routine may be called only within another routine body or inside a
-.Ic testing
-body.
-.It Ic testing Ar reason Ic dir Ar path Brq code ...
-Define a test case.
-.Ar reason
-is what the test block is about and
-.Ar path
-is the path to the root directory where the test will be executed.
-.Ar reason
-and
-.Ar path
-must be string literals.
-.El
-.Pp
-Inside a
-.Ic proc
-or
-.Ic testing
-code block the following instructions are allowed:
-.Bl -tag -width Ds
-.It Ar variable Cm = Ar expression
-Set a local lexical
-.Ar variable
-to the value yielded by
-.Ar expression .
-The
-.Ar variable
-lifetime last from this declaration until the end of the current
-block.
-.It Ar procedure Ns Pq Ar arguments ...
-Execute
-.Ar procedure
-with the given
-.Ar arguments .
-.It Ic assert Ar comparison
-Evaluate
-.Ar comparison
-and if it not yields a true-ish value terminate the current running
-test and mark it as failed.
-Multiple assertion can be done in one single
-.Ic assert
-block using the following syntax:
-.Bd -literal -offset Ds
-.Ic assert (
-	comparison_1
-	comparison_2
-	...
-	comparison_n
-)
-.Ed
-.Pp
-Note that newlines are mandatory after every
-.Ar comparison
-in this case.
-.It Ic should-fail Ar expression Op : Ar reason
-Evaluate
-.Ar expression
-and continue only if the evaluation produced an error.
-If the execution of
-.Ar expression
-is successful, terminate the current test.
-.Ar reason
-is optional and, if present, must be a literal string.
-It is similar to the
-.Sq try-catch
-statement of other programming languages.
-.El
-.Sh BUILT IN FUNCTIONS
-These functions are built into the language and provided by the
-interpreter:
-.Bl -tag -width Ds
-.It Ic debug Ns Po Ar arg, ... Pc
-Print the argument list separated by a space and followed by a newline
-if the interpreter runs with the verbose flag set.
-.It Ic iota Ns Pq
-Return distinct u16 integer every time it's called.
-Starts at zero and goes up to 254 to then wrap around to zero again.
-255 is skipped because
-.Ic iota
-is intended to be used to provide the tag for
-.Ic send
-and 255 is the special
-.Sq NOTAG
-value in 9P200.
-.It Ic print Ns Po Ar arg, ... Pc
-Print the argument list separated by a space and followed by a
-newline.
-.It Ic recv Ns Pq
-Receive a message from the server and return it as an object.
-A
-.Dv Terror
-doesn't stop the execution of the test, rather, an error object is
-returned.
-See
-.Sx OBJECTS AND FIELDS
-for the complete list of objects.
-.It Ic send Ns Po Ar type, tag, ... Pc
-Send a 9P message with the given
-.Ar type
-and
-.Ar tag .
-Other arguments, if given, are packed into the message and sent as
-well, respecting the given order.
-The overall length of the message is computed automatically.
-.It Ic skip Ns Pq
-Terminate the execution of the current test suite immediately.
-The test won't be counted as passed nor failed, but as skipped.
-.El
-.Sh OBJECTS AND FIELDS
-List of objects and fields...
-.Sh SEE ALSO
-.Xr 9p 7 ,
-.Xr kamid 8 ,
-.Xr ninepscript 8
-.Sh AUTHORS
-.An -nosplit
-.Nm
-was designed and implemented by
-.An Omar Polo Aq Mt op@omarpolo.com
-for the
-.Xr kamid 8
-daemon regression suite.
blob - 2df71001f2c0ddff2c4217feba1820fecb17df36 (mode 644)
blob + /dev/null
--- ninepscript.8
+++ /dev/null
@@ -1,54 +0,0 @@
-.\" 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.
-.\"
-.Dd $Mdocdate: December 02 2021$
-.Dt NINEPSCRIPT 8
-.Os
-.Sh NAME
-.Nm ninepscript
-.Nd scripting language for the kamid regress suite
-.Sh SYNOPSIS
-.Nm
-.Op Fl nv
-.Op Fl x Ar pattern
-.Ar
-.Sh DESCRIPTION
-.Nm
-is an interpreter for a custom DSL
-.Pq domain sppecific language
-used to write the regression suite of
-.Xr kamid 8 .
-The test themselves are written in
-.Xr ninepscript 5 .
-.Pp
-The options are as follows:
-.Bl -tag -width Ds
-.It Fl n
-don't run the test, check only the syntax of the provided
-.Ar files .
-.It Fl v
-verbose logging, print more information during the tests execution.
-.It Fl x Ar pattern
-Run only the tests that match the given
-.Ar pattern .
-.El
-.Pp
-.Nm
-first loads all the given
-.Ar files
-then proceeds to execute each defined test.
-.Pp
-See
-.Xr ninepscript 5
-for the description of the ninepscript language.
blob - 3b22364359c6f834886ffda7912b580339df0524 (mode 644)
blob + /dev/null
--- np.y
+++ /dev/null
@@ -1,686 +0,0 @@
-/*
- * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
- * Copyright (c) 2018 Florian Obser <florian@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 "compat.h"
-
-#include <ctype.h>
-#include <errno.h>
-#include <inttypes.h>
-#include <limits.h>
-#include <limits.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <syslog.h>
-
-#include "log.h"
-#include "kamid.h"
-#include "utils.h"
-
-#include "script.h"
-
-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;
-	u_char			*ungetbuf;
-	int			 eof_reached;
-	int			 lineno;
-	int			 errors;
-} *file, *topfile;
-struct file	*pushfile(const char *);
-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);
-
-static int	shouldfail;
-
-typedef struct {
-	union {
-		struct op	*op;
-		struct proc	*proc;
-		char		*str;
-		int64_t		 num;
-	} v;
-	int		 lineno;
-} YYSTYPE;
-
-%}
-
-/*
- * for bison:
- * %define parse.error verbose
- */
-
-%token	ASSERT
-%token	CONST
-%token	DIR
-%token	ERROR
-%token	INCLUDE
-%token	PROC
-%token	REPEAT
-%token	SHOULD_FAIL STR
-%token	TESTING
-%token	U8 U16 U32
-%token	VARGS
-
-%token	<v.str>		STRING SYMBOL
-%token	<v.num>		NUMBER
-
-%type	<v.op>		cast cexpr check expr faccess funcall
-%type	<v.op>		literal sfail var varref vargs
-
-%type	<v.proc>	procname
-
-%%
-
-program : /* empty */
-	| program '\n'
-	| program include '\n'
-	| program const '\n'
-	| program proc '\n'
-	| program test '\n'
-	;
-
-optnl		: '\n' optnl		/* zero or more newlines */
-		| /*empty*/
-		;
-
-nl	: '\n' optnl ;
-
-include : INCLUDE STRING {
-		struct file	*nfile;
-
-		if ((nfile = pushfile($2)) == NULL) {
-			yyerror("failed to include file %s", $2);
-			free($2);
-			YYERROR;
-		}
-		free($2);
-
-		file = nfile;
-		lungetc('\n');
-	}
-	;
-
-const	: CONST consti
-	| CONST '(' optnl mconst ')'
-	;
-
-mconst	: consti nl | mconst consti nl ;
-
-consti	: SYMBOL '=' expr {
-		if (!global_set($1, $3)) {
-			yyerror("can't set %s: illegal expression", $1);
-			free($1);
-			free_op($3);
-			YYERROR;
-		}
-	}
-	;
-
-var	: SYMBOL '=' expr	{ $$ = op_assign($1, $3); }	;
-varref	: SYMBOL		{ $$ = op_var($1); }		;
-literal	: STRING		{ $$ = op_lit_str($1); }
-	| NUMBER		{ $$ = op_lit_num($1); }	;
-
-/*
- * `expr '=' '=' expr` is ambiguous.  furthermore, we're not
- * interested in checking all the possibilities here.
- */
-cexpr	: literal | varref | funcall | faccess ;
-check	: cexpr '=' '=' cexpr	{ $$ = op_cmp_eq($1, $4); }
-	| cexpr '<' '=' cexpr	{ $$ = op_cmp_leq($1, $4); }
-	;
-
-expr	: literal | funcall | varref | check | cast | faccess | vargs ;
-
-vargs	: VARGS		{ $$ = op_vargs(); }			;
-
-cast	: expr ':' U8	{ $$ = op_cast($1, V_U8); }
-	| expr ':' U16	{ $$ = op_cast($1, V_U16); }
-	| expr ':' U32	{ $$ = op_cast($1, V_U32); }
-	| expr ':' STR	{ $$ = op_cast($1, V_STR); }
-	;
-
-faccess	: varref '.' SYMBOL	{ $$ = op_faccess($1, $3); }
-	| faccess '.' SYMBOL	{ $$ = op_faccess($1, $3); }
-	;
-
-procname: SYMBOL {
-		if (($$ = proc_by_name($1)) == NULL) {
-			yyerror("unknown proc %s", $1);
-			free($1);
-			YYERROR;
-		}
-		free($1);
-	}
-	;
-
-funcall	: procname {
-		prepare_funcall();
-	} '(' args optcomma ')' {
-		struct proc	*proc;
-		int		 argc;
-
-		$$ = op_funcall($1);
-		proc = $$->v.funcall.proc;
-		argc = $$->v.funcall.argc;
-
-		if (argc != proc->minargs && !proc->vararg) {
-			yyerror("invalid arity for `%s': want %d arguments "
-			    "but %d given.", $1->name, proc->minargs, argc);
-			/* TODO: recursively free $$ */
-			YYERROR;
-		}
-
-		if (argc < proc->minargs && proc->vararg) {
-			yyerror("invalid arity for `%s': want at least %d "
-			    "arguments but %d given.", $1->name, proc->minargs,
-			    argc);
-			/* TODO: recursively free $$ */
-			YYERROR;
-		}
-	}
-	;
-
-optcomma: /* empty */ | ',' ;
-
-dots	: '.' '.' '.' ;
-
-args	: /* empty */
-	| args ',' expr	{ push_arg($3); }
-	| args ',' dots	{ push_arg(op_rest()); }
-	| expr		{ push_arg($1); }
-	| dots		{ push_arg(op_rest()); }
-	;
-
-proc	: PROC SYMBOL {
-		prepare_proc();
-	} '(' args ')' {
-		if (!proc_setup_body()) {
-			yyerror("invalid argument in proc `%s' definition",
-			    $2);
-			free($2);
-			YYERROR;
-		}
-	} '{' optnl block '}' {
-		proc_done($2);
-	}
-	;
-
-block	: /* empty */
-	| block var nl		{ block_push($2); }
-	| block funcall nl	{ block_push($2); }
-	| block assert nl
-	| block sfail nl	{ block_push($2); }
-	;
-
-sfail	: SHOULD_FAIL expr		{ $$ = op_sfail($2, NULL); }
-	| SHOULD_FAIL expr ':' STRING	{ $$ = op_sfail($2, $4); }
-	;
-
-assert	: ASSERT asserti
-	| ASSERT '(' optnl massert ')'
-	;
-
-massert	: asserti nl | massert asserti nl ;
-
-asserti	: check			{ block_push(op_assert($1)); }
-	;
-
-test	: TESTING STRING DIR STRING {
-		prepare_test();
-	} testopt '{' optnl block '}' {
-		test_done(shouldfail, $2, $4);
-		shouldfail = 0;
-	}
-	;
-
-testopt	: /* empty */
-	| SHOULD_FAIL	{ shouldfail = 1; }
-	;
-
-%%
-
-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)
-		fatalx("yyerror vasprintf");
-	va_end(ap);
-	logit(LOG_CRIT, "%s:%d: %s", 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[] = {
-		{"assert",	ASSERT},
-		{"const",	CONST},
-		{"dir",		DIR},
-		{"include",	INCLUDE},
-		{"proc",	PROC},
-		{"repeat",	REPEAT},
-		{"should-fail",	SHOULD_FAIL},
-		{"str",		STR},
-		{"testing",	TESTING},
-		{"u16",		U16},
-		{"u32",		U32},
-		{"u8",		U8},
-		{"vargs",	VARGS},
-	};
-	const struct keywords	*p;
-
-	p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
-	    sizeof(keywords[0]), kw_cmp);
-
-	if (p)
-		return p->k_val;
-	else
-		return SYMBOL;
-}
-
-#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, next;
-
-	if (quotec) {
-		if ((c = igetc()) == EOF) {
-			yyerror("reached end of file while parsing "
-			    "quoted string");
-			if (file == topfile || popfile() == EOF)
-				return EOF;
-			return quotec;
-		}
-		return c;
-	}
-
-	while ((c = igetc()) == '\\') {
-		next = igetc();
-		if (next != '\n') {
-			c = next;
-			break;
-		}
-		yylval.lineno = file->lineno;
-		file->lineno++;
-	}
-
-	if (c == EOF) {
-		/*
-		 * Fake EOL when hit EOF for the first time. This gets line
-		 * count right if last line in 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, "lungetc");
-		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;
-}
-
-
-#if 0
-int my_yylex(void);
-
-int
-yylex(void)
-{
-	int x;
-
-	switch (x = my_yylex()) {
-	case ASSERT: puts("assert"); break;
-	case CONST: puts("const"); break;
-	case DIR: puts("dir"); break;
-	case ERROR: puts("error"); break;
-	case INCLUDE: puts("include"); break;
-	case PROC: puts("proc"); break;
-	case REPEAT: puts("repeat"); break;
-	case STR: puts(":str"); break;
-	case TESTING: puts("testing"); break;
-	case U8: puts(":u8"); break;
-	case U16: puts(":u16"); break;
-	case U32: puts(":u32"); break;
-
-	case STRING: printf("string \"%s\"\n", yylval.v.str); break;
-	case SYMBOL: printf("symbol %s\n", yylval.v.str); break;
-	case NUMBER: printf("number %"PRIu64"\n", yylval.v.num); break;
-
-	default:
-		printf("character ");
-		if (x == '\n')
-			printf("\\n");
-		else
-			printf("%c", x);
-		printf(" [0x%x]", x);
-		printf("\n");
-		break;
-	}
-
-	return x;
-}
-
-int
-my_yylex(void)
-#else
-int
-yylex(void)
-#endif
-{
-	unsigned char	 buf[8096];
-	unsigned char	*p;
-	int		 quotec, next, c;
-	int		 token;
-
-	p = buf;
-	while ((c = lgetc(0)) == ' ' || c == '\t' || c == '\f')
-		; /* nop */
-
-	yylval.lineno = file->lineno;
-	if (c == '#')
-		while ((c = lgetc(0)) != '\n' && c != EOF)
-			; /* nop */
-
-	switch (c) {
-	case ':':
-		return c;
-		break;
-	case '\'':
-	case '\"':
-		quotec = c;
-		while (1) {
-			if ((c = lgetc(quotec)) == EOF)
-				return 0;
-			if (c == '\n') {
-				file->lineno++;
-				continue;
-			} else if (c == '\\') {
-				if ((next = lgetc(quotec)) == EOF)
-					return 0;
-				if (next == quotec || next == ' ' ||
-				    next == '\t')
-					c = next;
-				else if (next == '\n') {
-					file->lineno++;
-					continue;
-				} else
-					lungetc(next);
-			} else if (c == quotec) {
-				*p = '\0';
-				break;
-			} else if (c == '\0') {
-				yyerror("syntax error");
-				return findeol();
-			}
-
-			if (p + 1 >= buf + sizeof(buf) - 1) {
-				yyerror("string too long");
-				return findeol();
-			}
-
-			*p++ = c;
-		}
-
-		yylval.v.str = xstrdup(buf);
-		return STRING;
-	}
-
-#define allowed_to_end_number(x) \
-	(isspace(x) || x == ')' || x == ',' || x == '/' || x == '}' \
-	    || x == '=' || x == ':')
-
-	if (c == '-' || isdigit(c)) {
-		do {
-			*p++ = c;
-			if ((size_t)(p-buf) >= sizeof(buf)) {
-				yyerror("string too long");
-				return findeol();
-			}
-		} while ((c = lgetc(0)) != EOF && (isdigit(c) || c == 'x'));
-		lungetc(c);
-		if (p == buf + 1 && buf[0] == '-')
-			goto nodigits;
-		if (c == EOF || allowed_to_end_number(c)) {
-			char *ep;
-
-			*p = '\0';
-			errno = 0;
-			yylval.v.num = strtoll(buf, &ep, 0);
-			if (*ep != '\0' || (errno == ERANGE &&
-			    (yylval.v.num == LONG_MAX ||
-			    yylval.v.num == LONG_MIN))) {
-				yyerror("\"%s\" invalid number or out of range",
-				    buf);
-				return findeol();
-			}
-
-			return NUMBER;
-		} else {
-nodigits:
-			while (p > buf + 1)
-				lungetc(*--p);
-			c = *--p;
-			if (c == '-')
-				return c;
-		}
-	}
-
-#define allowed_in_symbol(x)				\
-	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
-	    x != '{' && x != '}' && \
-	    x != '!' && x != '=' && \
-	    x != '#' && x != ',' && \
-	    x != '.' && x != ':'))
-
-	if (isalnum(c) || c == ':' || c == '_') {
-		do {
-			*p++ = c;
-			if ((size_t)(p-buf) >= sizeof(buf)) {
-				yyerror("string too long");
-				return findeol();
-			}
-		} while ((c = lgetc(0)) != EOF && (allowed_in_symbol(c)));
-		lungetc(c);
-		*p = '\0';
-		if ((token = lookup(buf)) == SYMBOL)
-			yylval.v.str = xstrdup(buf);
-		return token;
-	}
-
-	if (c == '\n') {
-		yylval.lineno = file->lineno;
-		file->lineno++;
-	}
-	if (c == EOF)
-		return 0;
-	return c;
-}
-
-struct file *
-pushfile(const char *name)
-{
-	struct file	*nfile;
-
-	if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
-		log_warn("calloc");
-		return NULL;
-	}
-	if ((nfile->name = strdup(name)) == NULL) {
-		log_warn("strdup");
-		free(nfile);
-		return NULL;
-	}
-	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
-		log_warn("%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) {
-		log_warn("malloc");
-		fclose(nfile->stream);
-		free(nfile->name);
-		free(nfile);
-		return NULL;
-	}
-	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;
-}
-
-void
-loadfile(const char *path)
-{
-	int errors;
-
-	file = pushfile(path);
-	if (file == NULL)
-		err(1, "pushfile");
-	topfile = file;
-
-	yyparse();
-	errors = file->errors;
-	popfile();
-
-	if (errors)
-		errx(1, "can't load %s because of errors", path);
-}
blob - 946f6669b45b632adfcbc3bd1920d2554b3ebc5e (mode 644)
blob + /dev/null
--- parse.y
+++ /dev/null
@@ -1,987 +0,0 @@
-/*
- * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
- * Copyright (c) 2018 Florian Obser <florian@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 "compat.h"
-
-#include <sys/stat.h>
-
-#include <ctype.h>
-#include <err.h>
-#include <errno.h>
-#include <event.h>
-#include <inttypes.h>
-#include <limits.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <syslog.h>
-#include <unistd.h>
-
-#include "log.h"
-#include "kamid.h"
-#include "table.h"
-#include "utils.h"
-
-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;
-	u_char			*ungetbuf;
-	int			 eof_reached;
-	int			 lineno;
-	int			 errors;
-} *file, *topfile;
-struct file	*pushfile(const char *, int);
-int		 popfile(void);
-int		 check_file_secrecy(int, const char *);
-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);
-
-TAILQ_HEAD(symhead, sym)	 symhead = TAILQ_HEAD_INITIALIZER(symhead);
-struct sym {
-	TAILQ_ENTRY(sym)	 entry;
-	int			 used;
-	int			 persist;
-	char			*nam;
-	char			*val;
-};
-
-int	 symset(const char *, const char *, int);
-char	*symget(const char *);
-
-void	 clear_config(struct kd_conf *xconf);
-
-static void		 add_table(const char *, const char *, const char *);
-static struct table	*findtable(const char *name);
-static void		 add_cert(const char *, const char *);
-static void		 add_key(const char *, const char *);
-static struct kd_listen_conf *listen_new(void);
-
-static uint32_t			 counter;
-static struct table		*table;
-static struct kd_listen_conf	*listener;
-static struct kd_conf		*conf;
-static int			 errors;
-
-typedef struct {
-	union {
-		int64_t		 number;
-		char		*string;
-		struct table	*table;
-	} v;
-	int lineno;
-} YYSTYPE;
-
-%}
-
-%token	AUTH
-%token	CERT
-%token	ERROR
-%token	INCLUDE
-%token	KEY
-%token	LISTEN
-%token	NO
-%token	ON
-%token	PKI PORT
-%token	TABLE TLS
-%token	USERDATA
-%token	VIRTUAL
-%token	YES
-
-%token	<v.string>	STRING
-%token	<v.number>	NUMBER
-%type	<v.number>	yesno
-%type	<v.string>	string
-%type	<v.table>	tableref
-
-%%
-
-grammar		: /* empty */
-		| grammar include '\n'
-		| grammar '\n'
-		| grammar table '\n'
-		| grammar pki '\n'
-		| grammar listen '\n'
-		| grammar varset '\n'
-		| grammar error '\n'		{ 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');
-		}
-		;
-
-string		: string STRING	{
-			if (asprintf(&$$, "%s %s", $1, $2) == -1) {
-				free($1);
-				free($2);
-				yyerror("string: asprintf");
-				YYERROR;
-			}
-			free($1);
-			free($2);
-		}
-		| STRING
-		;
-
-yesno		: YES	{ $$ = 1; }
-		| NO	{ $$ = 0; }
-		;
-
-optnl		: '\n' optnl		/* zero or more newlines */
-		| /*empty*/
-		;
-
-nl		: '\n' optnl		/* one or more newlines */
-		;
-
-arrow		: '=' '>' ;
-
-comma		: ',' optnl
-		;
-
-varset		: STRING '=' string		{
-			char *s = $1;
-			if (verbose)
-				printf("%s = \"%s\"\n", $1, $3);
-			while (*s++) {
-				if (isspace((unsigned char)*s)) {
-					yyerror("macro name cannot contain "
-					    "whitespace");
-					free($1);
-					free($3);
-					YYERROR;
-				}
-			}
-			if (symset($1, $3, 0) == -1)
-				fatal("cannot store variable");
-			free($1);
-			free($3);
-		}
-		;
-
-pki		: PKI STRING CERT STRING { add_cert($2, $4); }
-		| PKI STRING KEY STRING  { add_key($2, $4); }
-		;
-
-table_kp	: string arrow string optnl {
-			if (table_add(table, $1, $3) == -1)
-				yyerror("can't add to table %s",
-				    table->t_name);
-			free($1);
-			free($3);
-		}
-		;
-
-table_kps	: table_kp
-		| table_kp comma table_kps
-		;
-
-stringel	: STRING {
-			if (table_add(table, $1, NULL) == -1)
-				yyerror("can't add to table %s",
-				    table->t_name);
-			free($1);
-		}
-		;
-
-string_list	: stringel
-		| stringel comma string_list
-		;
-
-table_vals	: table_kps
-		| string_list
-		;
-
-table		: TABLE STRING STRING {
-			char *p;
-
-			if ((p = strchr($3, ':')) == NULL) {
-				yyerror("invalid table %s", $2);
-				YYERROR;
-			}
-
-			*p = '\0';
-			add_table($2, $3, p+1);
-			free($2);
-			free($3);
-		}
-		| TABLE STRING {
-			add_table($2, "static", NULL);
-		} '{' optnl table_vals '}' {
-			table = NULL;
-		}
-		;
-
-tableref	: '<' STRING '>' {
-			struct table *t;
-
-			t = findtable($2);
-			free($2);
-			if (t == NULL)
-				YYERROR;
-			$$ = t;
-		}
-		;
-
-listen		: LISTEN { listener = listen_new(); }
-		listen_opts {
-			if (listener->auth_table == NULL)
-				yyerror("missing auth table");
-			if (!(listener->flags & L_TLS))
-				yyerror("can't define a non-tls listener");
-			listener = NULL;
-		}
-		;
-
-listen_opts	: listen_opt
-		| listen_opt listen_opts
-		;
-
-listen_opt	: ON STRING PORT NUMBER	{
-			if (*listener->iface != '\0')
-				yyerror("listen address and port already"
-				    " defined");
-			strlcpy(listener->iface, $2, sizeof(listener->iface));
-			listener->port = $4;
-		}
-		| TLS PKI STRING {
-			if (*listener->pki != '\0')
-				yyerror("listen tls pki already defined");
-			listener->flags |= L_TLS;
-			strlcpy(listener->pki, $3, sizeof(listener->pki));
-		}
-		| AUTH tableref {
-			if (listener->auth_table != NULL)
-				yyerror("listen auth already defined");
-			listener->auth_table = $2;
-		}
-		| USERDATA tableref {
-			if (listener->userdata_table != NULL)
-				yyerror("userdata table already defined");
-			listener->userdata_table = $2;
-		}
-		| VIRTUAL tableref {
-			if (listener->virtual_table != NULL)
-				yyerror("virtual table already defined");
-			listener->virtual_table = $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)
-		fatalx("yyerror vasprintf");
-	va_end(ap);
-	logit(LOG_CRIT, "%s:%d: %s", 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[] = {
-		{"auth",		AUTH},
-		{"cert",		CERT},
-		{"include",		INCLUDE},
-		{"key",			KEY},
-		{"listen",		LISTEN},
-		{"no",			NO},
-		{"on",			ON},
-		{"pki",			PKI},
-		{"port",		PORT},
-		{"table",		TABLE},
-		{"tls",			TLS},
-		{"userdata",		USERDATA},
-		{"virtual",		VIRTUAL},
-		{"yes",			YES},
-	};
-	const struct keywords	*p;
-
-	p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
-	    sizeof(keywords[0]), kw_cmp);
-
-	if (p)
-		return p->k_val;
-	else
-		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, next;
-
-	if (quotec) {
-		if ((c = igetc()) == EOF) {
-			yyerror("reached end of file while parsing "
-			    "quoted string");
-			if (file == topfile || popfile() == EOF)
-				return EOF;
-			return quotec;
-		}
-		return c;
-	}
-
-	while ((c = igetc()) == '\\') {
-		next = igetc();
-		if (next != '\n') {
-			c = next;
-			break;
-		}
-		yylval.lineno = file->lineno;
-		file->lineno++;
-	}
-
-	if (c == EOF) {
-		/*
-		 * Fake EOL when hit EOF for the first time. This gets line
-		 * count right if last line in 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, "lungetc");
-		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;
-}
-
-#if 0
-int my_yylex(void);
-
-int
-yylex(void)
-{
-	int x;
-
-	switch (x = my_yylex()) {
-	case AUTH:
-		puts("auth");
-		break;
-	case CERT:
-		puts("cert");
-		break;
-	case ERROR:
-		puts("error");
-		break;
-	case INCLUDE:
-		puts("include");
-		break;
-	case KEY:
-		puts("key");
-		break;
-	case LISTEN:
-		puts("listen");
-		break;
-	case NO:
-		puts("no");
-		break;
-	case ON:
-		puts("on");
-		break;
-	case PKI:
-		puts("pki");
-		break;
-	case PORT:
-		puts("port");
-		break;
-	case TABLE:
-		puts("table");
-		break;
-	case TLS:
-		puts("tls");
-		break;
-	case YES:
-		puts("yes");
-		break;
-	case STRING:
-		printf("string \"%s\"\n", yylval.v.string);
-		break;
-	case NUMBER:
-		printf("number %"PRIi64"\n", yylval.v.number);
-	default:
-		printf("character ");
-		if (x == '\n')
-			printf("\\n");
-		else
-			printf("%c", x);
-		printf(" [0x%x]", x);
-		printf("\n");
-		break;
-	}
-
-	return x;
-}
-
-int
-my_yylex(void)
-#else
-int
-yylex(void)
-#endif
-{
-	char	 buf[8096];
-	char	*p, *val;
-	int	 quotec, next, c;
-	int	 token;
-
-top:
-	p = buf;
-	while ((c = lgetc(0)) == ' ' || c == '\t')
-		; /* nothing */
-
-	yylval.lineno = file->lineno;
-	if (c == '#')
-		while ((c = lgetc(0)) != '\n' && c != EOF)
-			; /* nothing */
-	if (c == '$' && !expanding) {
-		while (1) {
-			if ((c = lgetc(0)) == EOF)
-				return 0;
-
-			if (p + 1 >= buf + sizeof(buf) - 1) {
-				yyerror("string too long");
-				return findeol();
-			}
-			if (isalnum(c) || c == '_') {
-				*p++ = c;
-				continue;
-			}
-			*p = '\0';
-			lungetc(c);
-			break;
-		}
-		val = symget(buf);
-		if (val == NULL) {
-			yyerror("macro '%s' not defined", buf);
-			return findeol();
-		}
-		p = val + strlen(val) - 1;
-		lungetc(DONE_EXPAND);
-		while (p >= val) {
-			lungetc((unsigned char)*p);
-			p--;
-		}
-		lungetc(START_EXPAND);
-		goto top;
-	}
-
-	switch (c) {
-	case '\'':
-	case '"':
-		quotec = c;
-		while (1) {
-			if ((c = lgetc(quotec)) == EOF)
-				return 0;
-			if (c == '\n') {
-				file->lineno++;
-				continue;
-			} else if (c == '\\') {
-				if ((next = lgetc(quotec)) == EOF)
-					return (0);
-				if (next == quotec || next == ' ' ||
-				    next == '\t')
-					c = next;
-				else if (next == '\n') {
-					file->lineno++;
-					continue;
-				} else
-					lungetc(next);
-			} else if (c == quotec) {
-				*p = '\0';
-				break;
-			} else if (c == '\0') {
-				yyerror("syntax error");
-				return findeol();
-			}
-			if (p + 1 >= buf + sizeof(buf) - 1) {
-				yyerror("string too long");
-				return findeol();
-			}
-			*p++ = c;
-		}
-		yylval.v.string = strdup(buf);
-		if (yylval.v.string == NULL)
-			err(1, "yylex: strdup");
-		return STRING;
-	}
-
-#define allowed_to_end_number(x) \
-	(isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
-
-	if (c == '-' || isdigit(c)) {
-		do {
-			*p++ = c;
-			if ((size_t)(p-buf) >= sizeof(buf)) {
-				yyerror("string too long");
-				return findeol();
-			}
-		} while ((c = lgetc(0)) != EOF && isdigit(c));
-		lungetc(c);
-		if (p == buf + 1 && buf[0] == '-')
-			goto nodigits;
-		if (c == EOF || allowed_to_end_number(c)) {
-			const char *errstr = NULL;
-
-			*p = '\0';
-			yylval.v.number = strtonum(buf, LLONG_MIN,
-			    LLONG_MAX, &errstr);
-			if (errstr) {
-				yyerror("\"%s\" invalid number: %s",
-				    buf, errstr);
-				return findeol();
-			}
-			return NUMBER;
-		} else {
-nodigits:
-			while (p > buf + 1)
-				lungetc((unsigned char)*--p);
-			c = (unsigned char)*--p;
-			if (c == '-')
-				return c;
-		}
-	}
-
-#define allowed_in_string(x) \
-	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
-	x != '{' && x != '}' && \
-	x != '!' && x != '=' && x != '#' && \
-	x != ',' && x != '>'))
-
-	if (isalnum(c) || c == ':' || c == '_') {
-		do {
-			*p++ = c;
-			if ((size_t)(p-buf) >= sizeof(buf)) {
-				yyerror("string too long");
-				return findeol();
-			}
-		} while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
-		lungetc(c);
-		*p = '\0';
-		if ((token = lookup(buf)) == STRING)
-			if ((yylval.v.string = strdup(buf)) == NULL)
-				err(1, "yylex: strdup");
-		return token;
-	}
-	if (c == '\n') {
-		yylval.lineno = file->lineno;
-		file->lineno++;
-	}
-	if (c == EOF)
-		return 0;
-	return c;
-}
-
-int
-check_file_secrecy(int fd, const char *fname)
-{
-	struct stat	st;
-
-	if (fstat(fd, &st)) {
-		log_warn("cannot stat %s", fname);
-		return -1;
-	}
-	if (st.st_uid != 0 && st.st_uid != getuid()) {
-		log_warnx("%s: owner not root or current user", fname);
-		return -1;
-	}
-	if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
-		log_warnx("%s: group writable or world read/writable", fname);
-		return -1;
-	}
-	return 0;
-}
-
-struct file *
-pushfile(const char *name, int secret)
-{
-	struct file	*nfile;
-
-	if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
-		log_warn("calloc");
-		return NULL;
-	}
-	if ((nfile->name = strdup(name)) == NULL) {
-		log_warn("strdup");
-		free(nfile);
-		return NULL;
-	}
-	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
-		log_warn("%s", nfile->name);
-		free(nfile->name);
-		free(nfile);
-		return NULL;
-	} else if (secret &&
-	    check_file_secrecy(fileno(nfile->stream), nfile->name)) {
-		fclose(nfile->stream);
-		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) {
-		log_warn("malloc");
-		fclose(nfile->stream);
-		free(nfile->name);
-		free(nfile);
-		return NULL;
-	}
-	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;
-}
-
-struct kd_conf *
-parse_config(const char *filename)
-{
-	struct sym		*sym, *next;
-
-	counter = 0;
-	conf = config_new_empty();
-
-	file = pushfile(filename, 0);
-	if (file == NULL) {
-		free(conf);
-		return NULL;
-	}
-	topfile = file;
-
-	yyparse();
-	errors = file->errors;
-	popfile();
-
-	/* Free macros and check which have not been used. */
-	TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) {
-		if (verbose && !sym->used)
-			fprintf(stderr, "warning: macro '%s' not used\n",
-			    sym->nam);
-		if (!sym->persist) {
-			free(sym->nam);
-			free(sym->val);
-			TAILQ_REMOVE(&symhead, sym, entry);
-			free(sym);
-		}
-	}
-
-	if (errors) {
-		clear_config(conf);
-		return NULL;
-	}
-
-	return conf;
-}
-
-int
-symset(const char *nam, const char *val, int persist)
-{
-	struct sym	*sym;
-
-	TAILQ_FOREACH(sym, &symhead, entry) {
-		if (strcmp(nam, sym->nam) == 0)
-			break;
-	}
-
-	if (sym != NULL) {
-		if (sym->persist == 1)
-			return 0;
-		else {
-			free(sym->nam);
-			free(sym->val);
-			TAILQ_REMOVE(&symhead, sym, entry);
-			free(sym);
-		}
-	}
-	if ((sym = calloc(1, sizeof(*sym))) == NULL)
-		return -1;
-
-	sym->nam = strdup(nam);
-	if (sym->nam == NULL) {
-		free(sym);
-		return -1;
-	}
-	sym->val = strdup(val);
-	if (sym->val == NULL) {
-		free(sym->nam);
-		free(sym);
-		return -1;
-	}
-	sym->used = 0;
-	sym->persist = persist;
-	TAILQ_INSERT_TAIL(&symhead, sym, entry);
-	return 0;
-}
-
-int
-cmdline_symset(char *s)
-{
-	char	*sym, *val;
-	int	ret;
-
-	if ((val = strrchr(s, '=')) == NULL)
-		return -1;
-	sym = strndup(s, val - s);
-	if (sym == NULL)
-		errx(1, "%s: strndup", __func__);
-	ret = symset(sym, val + 1, 1);
-	free(sym);
-
-	return ret;
-}
-
-char *
-symget(const char *nam)
-{
-	struct sym	*sym;
-
-	TAILQ_FOREACH(sym, &symhead, entry) {
-		if (strcmp(nam, sym->nam) == 0) {
-			sym->used = 1;
-			return sym->val;
-		}
-	}
-	return NULL;
-}
-
-void
-clear_config(struct kd_conf *xconf)
-{
-	/* free stuff? */
-
-	free(xconf);
-}
-
-static void
-add_table(const char *name, const char *type, const char *path)
-{
-	if (table_open(conf, name, type, path) == -1)
-		yyerror("can't initialize table %s", name);
-	table = STAILQ_FIRST(&conf->table_head)->table;
-}
-
-static struct table *
-findtable(const char *name)
-{
-	struct kd_tables_conf *i;
-
-	STAILQ_FOREACH(i, &conf->table_head, entry) {
-		if (!strcmp(i->table->t_name, name))
-			return i->table;
-	}
-
-	yyerror("unknown table %s", name);
-	return NULL;
-}
-
-static void
-add_cert(const char *name, const char *path)
-{
-	struct kd_pki_conf *pki;
-
-	STAILQ_FOREACH(pki, &conf->pki_head, entry) {
-		if (strcmp(name, pki->name) != 0)
-			continue;
-
-		if (pki->cert != NULL) {
-			yyerror("duplicate `pki %s cert'", name);
-			return;
-		}
-
-		goto set;
-	}
-
-	pki = xcalloc(1, sizeof(*pki));
-	strlcpy(pki->name, name, sizeof(pki->name));
-	STAILQ_INSERT_HEAD(&conf->pki_head, pki, entry);
-
-set:
-	if ((pki->cert = tls_load_file(path, &pki->certlen, NULL)) == NULL)
-		fatal(NULL);
-}
-
-static void
-add_key(const char *name, const char *path)
-{
-	struct kd_pki_conf *pki;
-
-	STAILQ_FOREACH(pki, &conf->pki_head, entry) {
-		if (strcmp(name, pki->name) != 0)
-			continue;
-
-		if (pki->key != NULL) {
-			yyerror("duplicate `pki %s key'", name);
-			return;
-		}
-
-		goto set;
-	}
-
-	pki = xcalloc(1, sizeof(*pki));
-	strlcpy(pki->name, name, sizeof(pki->name));
-	STAILQ_INSERT_HEAD(&conf->pki_head, pki, entry);
-
-set:
-	if ((pki->key = tls_load_file(path, &pki->keylen, NULL)) == NULL)
-		fatal(NULL);
-}
-
-static struct kd_listen_conf *
-listen_new(void)
-{
-	struct kd_listen_conf *l;
-
-	l = xcalloc(1, sizeof(*l));
-	l->id = counter++;
-	l->fd = -1;
-
-	STAILQ_INSERT_HEAD(&conf->listen_head, l, entry);
-	return l;
-}
blob - 5f149ef222c04f85943c9bcd2d100590e8f68d31 (mode 644)
blob + /dev/null
--- regress/consts.9ps
+++ /dev/null
@@ -1,46 +0,0 @@
-const (
-	np2000	= "9P2000"
-	msize	= 4194304:u32	# 4*1024*1024
-	notag	= -1:u16
-	nofid	= -1:u32
-
-	QTDIR	= 0x80
-	# ...
-	QTFILE	= 0x0
-
-	OREAD	= 0
-	OWRITE	= 1
-	ORDWR	= 2
-	OEXEC	= 3
-	OTRUNC	= 16
-	ORCLOSE	= 64
-
-	Tversion	= 100:u8
-	Rversion	= 101:u8
-	Tauth		= 102:u8
-	Rauth		= 103:u8
-	Tattach		= 104:u8
-	Rattach		= 105:u8
-	Terror		= 106:u8 # illegal
-	Rerror		= 107:u8
-	Tflush		= 108:u8
-	Rflush		= 109:u8
-	Twalk		= 110:u8
-	Rwalk		= 111:u8
-	Topen		= 112:u8
-	Ropen		= 113:u8
-	Tcreate		= 114:u8
-	Rcreate		= 115:u8
-	Tread		= 116:u8
-	Rread		= 117:u8
-	Twrite		= 118:u8
-	Rwrite		= 119:u8
-	Tclunk		= 120:u8
-	Rclunk		= 121:u8
-	Tremove		= 122:u8
-	Rremove		= 123:u8
-	Tstat		= 124:u8
-	Rstat		= 125:u8
-	Twstat		= 126:u8
-	Rwstat		= 127:u8
-)
blob - /dev/null
blob + c0aa58ac4c6f1883a9f4d63cc216253cbf5ff1a0 (mode 644)
--- /dev/null
+++ regress/Makefile
@@ -0,0 +1,9 @@
+HAVE_LISP ?= No
+
+SUBDIR = ninepscript
+
+.if ${HAVE_LISP:L} == yes
+SUBDIR += lisp
+.endif
+
+.include <bsd.subdir.mk>
blob - aca3357c55180745ed17f5a5fb9eb5ef56bd64e0 (mode 644)
blob + /dev/null
--- regress/io-suite.9ps
+++ /dev/null
@@ -1,41 +0,0 @@
-include "lib.9ps"
-
-testing "open + clunk works" dir "./root" {
-	mount(0, "/")
-	walk(0, 1, "dir", "subdir", "file")
-	expect(Rwalk)
-
-	open(1, OREAD)
-	m = recv()
-	assert m.type == Ropen
-
-	clunk(1)
-	m = recv()
-	assert m.type == Rclunk
-}
-
-testing "can open directories" dir "./root" {
-	mount(0, "/")
-	walk(0, 1, "dir", "subdir")
-	expect(Rwalk)
-
-	open(1, OREAD)
-	m = recv()
-	assert m.type == Ropen
-
-	clunk(1)
-	m = recv()
-	assert m.type == Rclunk
-}
-
-testing "can't open directories for writing" dir "./root" {
-	mount(0, "/")
-	walk(0, 1, "dir")
-	expect(Rwalk)
-
-	open(1, OWRITE)
-	expect-error()
-
-	open(1, ORDWR)
-	expect-error()
-}
blob - /dev/null
blob + d3581899e6d17bbfede4fae297d8f51b738879db (mode 644)
--- /dev/null
+++ regress/Makefile.inc
@@ -0,0 +1,2 @@
+.include "../kamid-version.mk"
+.include "../Makefile.inc"
blob - c5b25c870e154f06143d64be5bab83c66694c253 (mode 644)
blob + /dev/null
--- regress/lib.9ps
+++ /dev/null
@@ -1,58 +0,0 @@
-include "consts.9ps"
-
-# 9p protocol
-
-proc version(msize, version) {
-	send(Tversion, notag, msize:u32, version:str)
-}
-
-proc attach(fid, afid, uname, aname) {
-	send(Tattach, iota(), fid:u32, afid:u32, uname:str, aname:str)
-}
-
-proc walk(fid, newfid, ...) {
-	send(Twalk, iota(), fid:u32, newfid:u32, vargs:u16, ...)
-}
-
-proc open(fid, mode) {
-	send(Topen, iota(), fid:u32, mode:u8)
-}
-
-proc clunk(fid) {
-	send(Tclunk, iota(), fid:u32)
-}
-
-
-
-# useful functions
-
-proc mount(fid, path) {
-	version(msize, np2000)
-
-	m = recv()
-	assert (
-		m.type == Rversion
-		m.tag == notag
-		m.msize <= msize
-		# m.version == version
-	)
-
-	attach(fid, nofid, "op", path)
-
-	m = recv()
-	assert (
-		m.type == Rattach
-		m.qid.type == QTDIR
-	)
-}
-
-proc expect(t) {
-	m = recv()
-	assert m.type == t
-}
-
-proc expect-error() {
-	m = recv()
-	assert m.type == Rerror
-	debug("got expected error", m)
-}
blob - 598ecedc0ac2742c734e6bcf87aeb4388ea95b15 (mode 755)
blob + /dev/null
--- regress/lisp/9p-test/run-tests.sh
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/sh
-
-#export REGRESS_CERT="$HOME/lisp/kamid.cert"
-#export REGRESS_KEY="$HOME/lisp/kamid.key"
-#export REGRESS_HOSTNAME="localhost"
-#export REGRESS_PORT=10564
-
-sbcl --eval "(require 'asdf)" --eval "(push \"$(pwd)/\" asdf:*central-registry*)" --eval "(asdf:make \"9p-test\")" --eval "(all-tests:run-all-tests)"
blob - /dev/null
blob + e14bb17cce917914d9bb94daa608f41e808a31c0 (mode 644)
--- /dev/null
+++ regress/lisp/Makefile
@@ -0,0 +1,7 @@
+REGRESS_TARGETS= lisp
+NOOBJ=Yes
+
+lisp:
+	SUDO=${SUDO} ./run.sh
+
+.include <bsd.regress.mk>
blob - /dev/null
blob + a2038af9311ed5befcd1c0324c6442a59b5976f2 (mode 755)
--- /dev/null
+++ regress/lisp/run.sh
@@ -0,0 +1,105 @@
+#!/bin/sh
+#
+# Run external tests, requires a common lisp interpreter (sbcl by
+# default) to be available.
+
+SUDO=${SUDO:-doas}
+USER=${USER?:user not set}
+SBCL=${SBCL:-sbcl}
+
+set -e
+
+if ! which kamid 2>/dev/null >/dev/null; then
+	echo "can't find kamid in PATH" >&2
+	exit 1
+fi
+
+# gencerts name
+gencerts() {
+	echo "generating keypairs for $1..."
+	openssl req -x509		\
+		-newkey rsa:4096	\
+		-out "$1.pem"		\
+		-keyout "$1.key"	\
+		-days 365		\
+		-nodes			\
+		-subj "/CN=$1"
+}
+
+# h cert
+h() {
+	printf "SHA256:"
+	openssl x509 -in "$1" -noout -fingerprint -sha256 | \
+		sed -e 's/^.*=//' -e 's/://g' | \
+		tr A-Z a-z
+}
+
+if [ ! -f client.pem -o ! -f client.key ]; then
+	gencerts client
+fi
+
+if [ ! -f kamid.pem -o ! -f kamid.key ]; then
+	gencerts kamid
+fi
+
+kamid_hash="$(h client.pem)"
+tmpdir="$(mktemp -d -t kamid-regress.XXXXXXXXXX)"
+testroot="$tmpdir/root"
+
+cp -R ../root/ "$tmpdir"
+
+cat > regress.conf <<EOF
+pki localhost cert "$PWD/kamid.pem"
+pki localhost key  "$PWD/kamid.key"
+
+table users { "$kamid_hash" => "flan" }
+table virt  { "flan" => "$USER" }
+table data  { "flan" => "$testroot" }
+
+listen on localhost port 1337 tls pki localhost \
+	auth <users> \
+	virtual <virt> \
+	userdata <data>
+EOF
+
+logfile="$tmpdir/$(date +%Y-%m-%d-%H-%M).log"
+
+export REGRESS_CERT="$PWD/client.pem"
+export REGRESS_KEY="$PWD/client.key"
+export REGRESS_HOSTNAME=localhost
+export REGRESS_PORT=1337
+export REGRESS_ROOT="$testroot"
+
+echo "REGRESS_CERT: $REGRESS_CERT"
+echo "REGRESS_KEY: $REGRESS_KEY"
+echo "REGRESS_HOSTNAME: $REGRESS_HOSTNAME"
+echo "REGRESS_PORT: $REGRESS_PORT"
+echo "REGRESS_ROOT: $REGRESS_ROOT"
+echo
+
+echo "logging on $logfile"
+${SUDO} "$(which kamid)" -d -vvv -f regress.conf > "$logfile" 2>&1 &
+
+ret=0
+
+set +e
+cd 9p-test/ && \
+	${SBCL} --noinform \
+		--disable-debugger \
+		--eval "(require 'asdf)" \
+		--eval "(push \"$(pwd)/\" asdf:*central-registry*)" \
+		--eval "(asdf:make \"9p-test\")" \
+		--eval "(all-tests:run-all-tests)"
+
+ret=$?
+if [ $ret -ne 0 ]; then
+	echo
+	echo "Test failed, leaving root at $testroot"
+	sleep 1
+else
+	rm -rf "$testroot"
+fi
+
+${SUDO} pkill kamid
+
+exit $ret
blob - /dev/null
blob + ddd9794ed92d5c469e202ee924479a18e6c352b1 (mode 644)
--- /dev/null
+++ regress/ninepscript/Makefile
@@ -0,0 +1,7 @@
+REGRESS_TARGETS=suite
+NOOBJ=Yes
+
+suite:
+	${SUDO} ${PREFIX}/bin/ninepscript *-suite.9ps
+
+.include <bsd.regress.mk>
blob - /dev/null
blob + 5f149ef222c04f85943c9bcd2d100590e8f68d31 (mode 644)
--- /dev/null
+++ regress/ninepscript/consts.9ps
@@ -0,0 +1,46 @@
+const (
+	np2000	= "9P2000"
+	msize	= 4194304:u32	# 4*1024*1024
+	notag	= -1:u16
+	nofid	= -1:u32
+
+	QTDIR	= 0x80
+	# ...
+	QTFILE	= 0x0
+
+	OREAD	= 0
+	OWRITE	= 1
+	ORDWR	= 2
+	OEXEC	= 3
+	OTRUNC	= 16
+	ORCLOSE	= 64
+
+	Tversion	= 100:u8
+	Rversion	= 101:u8
+	Tauth		= 102:u8
+	Rauth		= 103:u8
+	Tattach		= 104:u8
+	Rattach		= 105:u8
+	Terror		= 106:u8 # illegal
+	Rerror		= 107:u8
+	Tflush		= 108:u8
+	Rflush		= 109:u8
+	Twalk		= 110:u8
+	Rwalk		= 111:u8
+	Topen		= 112:u8
+	Ropen		= 113:u8
+	Tcreate		= 114:u8
+	Rcreate		= 115:u8
+	Tread		= 116:u8
+	Rread		= 117:u8
+	Twrite		= 118:u8
+	Rwrite		= 119:u8
+	Tclunk		= 120:u8
+	Rclunk		= 121:u8
+	Tremove		= 122:u8
+	Rremove		= 123:u8
+	Tstat		= 124:u8
+	Rstat		= 125:u8
+	Twstat		= 126:u8
+	Rwstat		= 127:u8
+)
blob - /dev/null
blob + dd15078e8b5ab344f1694f8e354a235160b15ee7 (mode 644)
--- /dev/null
+++ regress/ninepscript/io-suite.9ps
@@ -0,0 +1,41 @@
+include "lib.9ps"
+
+testing "open + clunk works" dir "./../root" {
+	mount(0, "/")
+	walk(0, 1, "dir", "subdir", "file")
+	expect(Rwalk)
+
+	open(1, OREAD)
+	m = recv()
+	assert m.type == Ropen
+
+	clunk(1)
+	m = recv()
+	assert m.type == Rclunk
+}
+
+testing "can open directories" dir "./../root" {
+	mount(0, "/")
+	walk(0, 1, "dir", "subdir")
+	expect(Rwalk)
+
+	open(1, OREAD)
+	m = recv()
+	assert m.type == Ropen
+
+	clunk(1)
+	m = recv()
+	assert m.type == Rclunk
+}
+
+testing "can't open directories for writing" dir "./../root" {
+	mount(0, "/")
+	walk(0, 1, "dir")
+	expect(Rwalk)
+
+	open(1, OWRITE)
+	expect-error()
+
+	open(1, ORDWR)
+	expect-error()
+}
blob - /dev/null
blob + c5b25c870e154f06143d64be5bab83c66694c253 (mode 644)
--- /dev/null
+++ regress/ninepscript/lib.9ps
@@ -0,0 +1,58 @@
+include "consts.9ps"
+
+# 9p protocol
+
+proc version(msize, version) {
+	send(Tversion, notag, msize:u32, version:str)
+}
+
+proc attach(fid, afid, uname, aname) {
+	send(Tattach, iota(), fid:u32, afid:u32, uname:str, aname:str)
+}
+
+proc walk(fid, newfid, ...) {
+	send(Twalk, iota(), fid:u32, newfid:u32, vargs:u16, ...)
+}
+
+proc open(fid, mode) {
+	send(Topen, iota(), fid:u32, mode:u8)
+}
+
+proc clunk(fid) {
+	send(Tclunk, iota(), fid:u32)
+}
+
+
+
+# useful functions
+
+proc mount(fid, path) {
+	version(msize, np2000)
+
+	m = recv()
+	assert (
+		m.type == Rversion
+		m.tag == notag
+		m.msize <= msize
+		# m.version == version
+	)
+
+	attach(fid, nofid, "op", path)
+
+	m = recv()
+	assert (
+		m.type == Rattach
+		m.qid.type == QTDIR
+	)
+}
+
+proc expect(t) {
+	m = recv()
+	assert m.type == t
+}
+
+proc expect-error() {
+	m = recv()
+	assert m.type == Rerror
+	debug("got expected error", m)
+}
blob - /dev/null
blob + ab1b801eb99c1408f60afdd75fc9e706cf425010 (mode 644)
--- /dev/null
+++ regress/ninepscript/misc-suite.9ps
@@ -0,0 +1,61 @@
+include "lib.9ps"
+
+testing "if version works" dir "./../root" {
+	send(Tversion, notag, msize, np2000)
+	m = recv()
+	assert m.type == Rversion
+}
+
+testing "fails when sending a R-message" dir "./../root" {
+	send(Rversion, notag, msize, np2000)
+	should-fail recv() : "the connection should have been closed"
+}
+
+testing "multiple attach" dir "./../root" {
+	version(msize, np2000)
+
+	m = recv()
+	assert (
+		m.type == Rversion
+		m.tag == notag
+		m.msize <= msize
+	)
+
+	fid1 = 0
+	fid2 = 1
+
+	# attach the first fid
+	attach(fid1, nofid, "op", "/")
+	m = recv()
+	assert (
+		m.type == Rattach
+		m.qid.type == QTDIR
+	)
+
+	# attach the second fid
+	attach(fid2, nofid, "op", "/")
+	m = recv()
+	assert (
+		m.type == Rattach
+		m.qid.type == QTDIR
+	)
+}
+
+testing "don't close used qids" dir "./../root" {
+	mount(0, "/")
+
+	walk(0, 2, "dir")
+	expect(Rwalk)
+
+	clunk(0)
+	expect(Rclunk)
+
+	walk(2, 3, "a-file")
+	expect(Rwalk)
+
+	clunk(2)
+	expect(Rclunk)
+
+	open(3, OREAD)
+	expect(Ropen)
+}
blob - /dev/null
blob + feb818b939bbabc724d41d26f1fa77479299fe14 (mode 644)
--- /dev/null
+++ regress/ninepscript/sample.9ps
@@ -0,0 +1,54 @@
+# default protocol version
+const npversion = "9P2000"
+
+# some constants
+const (
+	one = 1:u8
+	two = 2
+
+	notag = -1:u8
+
+	n = 4
+)
+
+proc unreachable(qid) {		# and useless
+	type = qid.type
+	assert (
+		type == qid.type
+	)
+}
+
+proc test(x) {
+	skip()
+}
+
+proc myrealprint(...) {
+	print(...)
+}
+
+proc myprint(...) {
+	myrealprint(...)
+}
+
+testing "skip called in proc" dir "./root" {
+	foo = 5:u8
+	myprint("hello", "foo is", foo)
+	test(3:u32)
+	assert 1 == 0
+
+	assert (
+		5 == 7
+		7 == 9
+		8 == 0
+	)
+}
+
+testing "casts" dir "./root" {
+	foo = 300:u8
+}
+
+proc empty() {
+}
+
+testing "foobar" dir "./root" {
+}
blob - /dev/null
blob + 8a0a8ca13b19b78d916dc6137b301ea2a0dfa2a2 (mode 644)
--- /dev/null
+++ regress/ninepscript/walk-suite.9ps
@@ -0,0 +1,74 @@
+include "lib.9ps"
+
+# TODO: add a test that tries to do a walk after opening a fid for i/o.
+
+testing "walk to a directory" dir "./../root" {
+	mount(0, "/")
+	walk(0, 1, "dir", "subdir")
+
+	m = recv()
+	assert (
+		m.type == Rwalk
+		m.nwqid == 2
+		m.wqid.0.type == QTDIR
+		m.wqid.1.type == QTDIR
+	)
+}
+
+testing "walk to a file" dir "./../root" {
+	mount(0, "/")
+	walk(0, 1, "dir", "subdir", "file")
+
+	m = recv()
+	assert (
+		m.type == Rwalk
+		m.nwqid == 3
+		m.wqid.0.type == QTDIR
+		m.wqid.1.type == QTDIR
+		m.wqid.2.type == QTFILE
+	)
+}
+
+testing "can't walk from a file" dir "./../root" {
+	mount(0, "/")
+
+	walk(0, 1, "dir", "a-file")
+	expect(Rwalk)
+
+	walk(1, 2, "subdir", "file")
+	expect-error()
+}
+
+testing "walk with invalid fid" dir "./../root" {
+	mount(0, "/")
+	walk(1, 2)
+	expect-error()
+}
+
+testing "walk with empty string" dir "./../root" {
+	mount(0, "/")
+	walk(0, 1, "")
+	expect-error()
+}
+
+testing "walk to a non-existant file" dir "./../root" {
+	mount(0, "/")
+	walk(0, 1, "non-exists")
+	expect-error()
+}
+
+testing "walk with an invalid component" dir "./../root" {
+	mount(0, "/")
+	walk(0, 1, "/non-exists")
+	expect-error()
+}
+
+testing "zero-path walk don't reply with a qid" dir "./../root" {
+	mount(0, "/")
+	walk(0, 1)
+	m = recv()
+	assert (
+		m.type == Rwalk
+		m.nwqid == 0
+	)
+}
blob - 8c80fde28a266bf7c9947cfd555b284fd9d5f801 (mode 644)
blob + /dev/null
--- regress/misc-suite.9ps
+++ /dev/null
@@ -1,61 +0,0 @@
-include "lib.9ps"
-
-testing "if version works" dir "./root" {
-	send(Tversion, notag, msize, np2000)
-	m = recv()
-	assert m.type == Rversion
-}
-
-testing "fails when sending a R-message" dir "./root" {
-	send(Rversion, notag, msize, np2000)
-	should-fail recv() : "the connection should have been closed"
-}
-
-testing "multiple attach" dir "./root" {
-	version(msize, np2000)
-
-	m = recv()
-	assert (
-		m.type == Rversion
-		m.tag == notag
-		m.msize <= msize
-	)
-
-	fid1 = 0
-	fid2 = 1
-
-	# attach the first fid
-	attach(fid1, nofid, "op", "/")
-	m = recv()
-	assert (
-		m.type == Rattach
-		m.qid.type == QTDIR
-	)
-
-	# attach the second fid
-	attach(fid2, nofid, "op", "/")
-	m = recv()
-	assert (
-		m.type == Rattach
-		m.qid.type == QTDIR
-	)
-}
-
-testing "don't close used qids" dir "./root" {
-	mount(0, "/")
-
-	walk(0, 2, "dir")
-	expect(Rwalk)
-
-	clunk(0)
-	expect(Rclunk)
-
-	walk(2, 3, "a-file")
-	expect(Rwalk)
-
-	clunk(2)
-	expect(Rclunk)
-
-	open(3, OREAD)
-	expect(Ropen)
-}
blob - /dev/null
blob + e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 (mode 644)
blob - feb818b939bbabc724d41d26f1fa77479299fe14 (mode 644)
blob + /dev/null
--- regress/sample.9ps
+++ /dev/null
@@ -1,54 +0,0 @@
-# default protocol version
-const npversion = "9P2000"
-
-# some constants
-const (
-	one = 1:u8
-	two = 2
-
-	notag = -1:u8
-
-	n = 4
-)
-
-proc unreachable(qid) {		# and useless
-	type = qid.type
-	assert (
-		type == qid.type
-	)
-}
-
-proc test(x) {
-	skip()
-}
-
-proc myrealprint(...) {
-	print(...)
-}
-
-proc myprint(...) {
-	myrealprint(...)
-}
-
-testing "skip called in proc" dir "./root" {
-	foo = 5:u8
-	myprint("hello", "foo is", foo)
-	test(3:u32)
-	assert 1 == 0
-
-	assert (
-		5 == 7
-		7 == 9
-		8 == 0
-	)
-}
-
-testing "casts" dir "./root" {
-	foo = 300:u8
-}
-
-proc empty() {
-}
-
-testing "foobar" dir "./root" {
-}
blob - f35b4b10b72ba797360354489331a55ead1c9bb0 (mode 644)
blob + /dev/null
--- regress/walk-suite.9ps
+++ /dev/null
@@ -1,74 +0,0 @@
-include "lib.9ps"
-
-# TODO: add a test that tries to do a walk after opening a fid for i/o.
-
-testing "walk to a directory" dir "./root" {
-	mount(0, "/")
-	walk(0, 1, "dir", "subdir")
-
-	m = recv()
-	assert (
-		m.type == Rwalk
-		m.nwqid == 2
-		m.wqid.0.type == QTDIR
-		m.wqid.1.type == QTDIR
-	)
-}
-
-testing "walk to a file" dir "./root" {
-	mount(0, "/")
-	walk(0, 1, "dir", "subdir", "file")
-
-	m = recv()
-	assert (
-		m.type == Rwalk
-		m.nwqid == 3
-		m.wqid.0.type == QTDIR
-		m.wqid.1.type == QTDIR
-		m.wqid.2.type == QTFILE
-	)
-}
-
-testing "can't walk from a file" dir "./root" {
-	mount(0, "/")
-
-	walk(0, 1, "dir", "a-file")
-	expect(Rwalk)
-
-	walk(1, 2, "subdir", "file")
-	expect-error()
-}
-
-testing "walk with invalid fid" dir "./root" {
-	mount(0, "/")
-	walk(1, 2)
-	expect-error()
-}
-
-testing "walk with empty string" dir "./root" {
-	mount(0, "/")
-	walk(0, 1, "")
-	expect-error()
-}
-
-testing "walk to a non-existant file" dir "./root" {
-	mount(0, "/")
-	walk(0, 1, "non-exists")
-	expect-error()
-}
-
-testing "walk with an invalid component" dir "./root" {
-	mount(0, "/")
-	walk(0, 1, "/non-exists")
-	expect-error()
-}
-
-testing "zero-path walk don't reply with a qid" dir "./root" {
-	mount(0, "/")
-	walk(0, 1)
-	m = recv()
-	assert (
-		m.type == Rwalk
-		m.nwqid == 0
-	)
-}
blob - baec1e893829b2d9016a0e589066c0d5a60725dc (mode 755)
blob + /dev/null
--- run-extra-tests.sh
+++ /dev/null
@@ -1,100 +0,0 @@
-#!/bin/sh
-#
-# Run external tests, requires a common lisp interpreter (sbcl by
-# default) to be available.
-
-DOAS=${DOAS:-doas}
-USER=${USER?:user not set}
-SBCL=${SBCL:-sbcl}
-
-set -e
-
-# gencerts name
-gencerts() {
-	echo "generating keypairs for $1..."
-	openssl req -x509		\
-		-newkey rsa:4096	\
-		-out "$1.pem"		\
-		-keyout "$1.key"	\
-		-days 365		\
-		-nodes			\
-		-subj "/CN=$1"
-}
-
-# h cert
-h() {
-	printf "SHA256:"
-	openssl x509 -in "$1" -noout -fingerprint -sha256 | \
-		sed -e 's/^.*=//' -e 's/://g' | \
-		tr A-Z a-z
-}
-
-if [ ! -f client.pem -o ! -f client.key ]; then
-	gencerts client
-fi
-
-if [ ! -f kamid.pem -o ! -f kamid.key ]; then
-	gencerts kamid
-fi
-
-kamid_hash="$(h client.pem)"
-tmpdir="$(mktemp -d -t kamid-regress.XXXXXXXXXX)"
-testroot="$tmpdir/root"
-
-cp -R regress/root/ "$tmpdir"
-
-cat > regress.conf <<EOF
-pki localhost cert "$PWD/kamid.pem"
-pki localhost key  "$PWD/kamid.key"
-
-table users { "$kamid_hash" => "flan" }
-table virt  { "flan" => "$USER" }
-table data  { "flan" => "$testroot" }
-
-listen on localhost port 1337 tls pki localhost \
-	auth <users> \
-	virtual <virt> \
-	userdata <data>
-EOF
-
-logfile="$tmpdir/$(date +%Y-%m-%d-%H-%M).log"
-
-export REGRESS_CERT="$PWD/client.pem"
-export REGRESS_KEY="$PWD/client.key"
-export REGRESS_HOSTNAME=localhost
-export REGRESS_PORT=1337
-export REGRESS_ROOT="$testroot"
-
-echo "REGRESS_CERT: $REGRESS_CERT"
-echo "REGRESS_KEY: $REGRESS_KEY"
-echo "REGRESS_HOSTNAME: $REGRESS_HOSTNAME"
-echo "REGRESS_PORT: $REGRESS_PORT"
-echo "REGRESS_ROOT: $REGRESS_ROOT"
-echo
-
-echo "logging on $logfile"
-${DOAS} ./kamid -d -vvv -f regress.conf > "$logfile" 2>&1 &
-
-ret=0
-
-set +e
-cd regress/lisp/9p-test/ && \
-	${SBCL} --noinform \
-		--disable-debugger \
-		--eval "(require 'asdf)" \
-		--eval "(push \"$(pwd)/\" asdf:*central-registry*)" \
-		--eval "(asdf:make \"9p-test\")" \
-		--eval "(all-tests:run-all-tests)"
-
-ret=$?
-if [ $ret -ne 0 ]; then
-	echo
-	echo "Test failed, leaving root at $testroot"
-	sleep 1
-else
-	rm -rf "$testroot"
-fi
-
-${DOAS} pkill kamid
-
-exit $ret
blob - 391e5ae2869fe2556407765b51278e546d8ffdb1 (mode 755)
blob + /dev/null
--- run-tests.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/sh
-#
-# Test runner for kamid
-
-set -e
-
-cd regress
-
-./../ninepscript "$@" *-suite.9ps
blob - cf1ac0e9438622950da2943dd9476e842bc8531c (mode 644)
blob + /dev/null
--- sandbox.c
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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 "log.h"
-#include "sandbox.h"
-
-#ifdef __OpenBSD__
-
-#include <unistd.h>
-
-void
-sandbox_main(void)
-{
-	return;
-}
-
-void
-sandbox_listener(void)
-{
-	return;
-}
-
-void
-sandbox_client(void)
-{
-	return;
-}
-
-#else
-#warning "No sandbox available for this OS"
-
-void
-sandbox_main(void)
-{
-	log_warnx("No sandbox available for this os");
-	return;
-}
-
-void
-sandbox_listener(void)
-{
-	return;
-}
-
-void
-sandbox_client(void)
-{
-	return;
-}
-
-#endif
blob - c964b983ea10e3af850adc627f269bd1051a2619 (mode 644)
blob + /dev/null
--- sandbox.h
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef SANDBOX_H
-#define SANDBOX_H
-
-void	sandbox_main(void);
-void	sandbox_listener(void);
-void	sandbox_client(void);
-
-#endif
blob - 8b480c2ff8bd568c4adadc410eed846e8f7696d2 (mode 644)
blob + /dev/null
--- script.c
+++ /dev/null
@@ -1,1768 +0,0 @@
-/*
- * 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 "compat.h"
-
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/wait.h>
-
-#include <assert.h>
-#include <endian.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <poll.h>
-#include <pwd.h>
-#include <regex.h>
-#include <signal.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <syslog.h>
-#include <unistd.h>
-
-#include "client.h"
-#include "log.h"
-#include "script.h"
-#include "utils.h"
-
-#define DEBUG 0
-
-#ifndef INFTIM
-#define INFTIM -1
-#endif
-
-static const char	*argv0;
-
-static uint8_t		*lastmsg;
-
-static struct imsgbuf	 ibuf;
-static int		 ibuf_inuse;
-static int		 child_out = -1;
-
-static struct procs procs = TAILQ_HEAD_INITIALIZER(procs);
-static struct tests tests = TAILQ_HEAD_INITIALIZER(tests);
-
-static int ntests;
-
-static struct opstacks blocks = TAILQ_HEAD_INITIALIZER(blocks);
-static struct opstacks args   = TAILQ_HEAD_INITIALIZER(args);
-
-#define STACK_HEIGHT 64
-static struct value	vstack[STACK_HEIGHT];
-static int		stackh;
-
-static struct envs envs = TAILQ_HEAD_INITIALIZER(envs);
-
-static struct value v_false = {.type = V_NUM, .v = {.num = 0}};
-static struct value v_true  = {.type = V_NUM, .v = {.num = 1}};
-
-static uint8_t lasttag;
-
-static int debug;
-static int syntaxcheck;
-
-static const char	*filler;
-
-static inline void
-before_printing(void)
-{
-	if (filler != NULL) {
-		printf("%s", filler);
-		filler = NULL;
-	}
-}
-
-static inline void
-check_for_output(void)
-{
-	static char	buf[BUFSIZ];
-	struct pollfd	pfd;
-	ssize_t		r;
-
-	pfd.fd = child_out;
-	pfd.events = POLLIN;
-	if (poll(&pfd, 1, 0) == -1)
-		fatal("poll");
-
-	if (!(pfd.revents & POLLIN))
-		return;
-
-	for (;;) {
-		if ((r = read(child_out, buf, sizeof(buf))) == -1) {
-			if (errno == EAGAIN)
-				break;
-			fatal("read");
-		}
-		if (r == 0)
-			break;
-		before_printing();
-		fwrite(buf, 1, r, stdout);
-	}
-}
-
-static inline void
-peekn(int depth, struct value *v)
-{
-	if (depth > stackh)
-		errx(1, "can't peek the stack at %d: underflow",
-		    depth);
-	memcpy(v, &vstack[stackh - depth], sizeof(*v));
-
-#if DEBUG
-	printf("peeking(%d) ", depth); pp_val(v); printf("\n");
-#endif
-}
-
-static inline void
-popv(struct value *v)
-{
-	if (stackh == 0)
-		errx(1, "can't pop the stack: underflow");
-	memcpy(v, &vstack[--stackh], sizeof(*v));
-
-#if DEBUG
-	printf("popping "); pp_val(v); printf("\n");
-#endif
-}
-
-static inline void
-popvn(int n)
-{
-	struct value v;
-
-	while (n-- > 0)
-		popv(&v);
-}
-
-static inline void
-pushv(struct value *v)
-{
-	if (stackh == STACK_HEIGHT)
-		errx(1, "can't push the stack: overflow");
-
-#if DEBUG
-	printf("pushing "); pp_val(v); printf("\n");
-#endif
-
-	memcpy(&vstack[stackh++], v, sizeof(*v));
-}
-
-static inline void
-pushbool(int n)
-{
-	pushv(n ? &v_true : &v_false);
-}
-
-static inline void
-pushnum(int64_t n)
-{
-	struct value v;
-
-	v.type = V_NUM;
-	v.v.num = n;
-	pushv(&v);
-}
-
-static inline struct opstack *
-pushstack(struct opstacks *stack)
-{
-	struct opstack *ops;
-
-	ops = xcalloc(1, sizeof(*ops));
-	TAILQ_INSERT_HEAD(stack, ops, entry);
-	return ops;
-}
-
-static inline struct opstack *
-peek(struct opstacks *stack)
-{
-	if (TAILQ_EMPTY(stack))
-		errx(1, "%s: args underflow", __func__);
-
-	return TAILQ_FIRST(stack);
-}
-
-static inline struct op *
-finalize(struct opstacks *stack, int *argc)
-{
-	struct opstack	*ops;
-	struct op	*op;
-
-	if (TAILQ_EMPTY(stack))
-		errx(1, "%s: args underflow", __func__);
-
-	ops = peek(stack);
-	TAILQ_REMOVE(&args, ops, entry);
-	op = ops->base.next;
-
-	if (argc != NULL)
-		*argc = ops->counter;
-
-	free(ops);
-	return op;
-}
-
-static inline void
-push(struct opstacks *stack, struct op *op)
-{
-	struct opstack *ops;
-
-	ops = peek(stack);
-	if (ops->last == NULL) {
-		ops->base.next = op;
-		ops->last = op;
-	} else {
-		ops->last->next = op;
-		ops->last = op;
-	}
-
-	ops->counter++;
-}
-
-static inline void
-pushenv(void)
-{
-	struct env	*e;
-
-	e = xcalloc(1, sizeof(*e));
-	TAILQ_INSERT_HEAD(&envs, e, entry);
-}
-
-static inline struct env *
-currentenv(void)
-{
-	assert(!TAILQ_EMPTY(&envs));
-	return TAILQ_FIRST(&envs);
-}
-
-static void
-popenv(void)
-{
-	struct env	*e;
-	struct binding	*b, *tb;
-
-	e = currentenv();
-	TAILQ_REMOVE(&envs, e, entry);
-
-	TAILQ_FOREACH_SAFE(b, &e->bindings, entry, tb)
-		free(b);
-
-	free(e);
-}
-
-static inline int
-setvar(char *sym, struct op *op)
-{
-	struct binding	*b;
-	struct env	*e;
-	int		 ret, height;
-
-	height = stackh;
-	if ((ret = eval(op)) != EVAL_OK)
-		return ret;
-
-	if (stackh != height + 1) {
-		before_printing();
-		printf("trying to assign to `%s' a void value: ", sym);
-		pp_op(op);
-		printf("\n");
-		return EVAL_ERR;
-	}
-
-	b = xcalloc(1, sizeof(*b));
-	b->name = sym;
-	popv(&b->val);
-
-	e = TAILQ_FIRST(&envs);
-	TAILQ_INSERT_HEAD(&e->bindings, b, entry);
-
-	return EVAL_OK;
-}
-
-static inline void
-setvar_raw(char *sym, struct op *op)
-{
-	struct binding	*b;
-	struct env	*e;
-
-	b = xcalloc(1, sizeof(*b));
-	b->name = sym;
-	b->raw = op;
-
-	e = TAILQ_FIRST(&envs);
-	TAILQ_INSERT_HEAD(&e->bindings, b, entry);
-}
-
-static inline int
-getvar(const char *sym, struct value *v)
-{
-	struct env	*e;
-	struct binding	*b;
-
-	TAILQ_FOREACH(e, &envs, entry) {
-		TAILQ_FOREACH(b, &e->bindings, entry) {
-			if (!strcmp(sym, b->name)) {
-				memcpy(v, &b->val, sizeof(*v));
-				return EVAL_OK;
-			}
-		}
-	}
-
-	before_printing();
-	fprintf(stderr, "unbound variable %s\n", sym);
-	return EVAL_ERR;
-}
-
-static inline int
-getvar_raw(const char *sym, struct op **raw)
-{
-	struct env	*e;
-	struct binding	*b;
-
-	TAILQ_FOREACH(e, &envs, entry) {
-		TAILQ_FOREACH(b, &e->bindings, entry) {
-			if (!strcmp(sym, b->name)) {
-				*raw = b->raw;
-				return EVAL_OK;
-			}
-		}
-	}
-
-	return EVAL_ERR;
-}
-
-int
-global_set(char *sym, struct op *op)
-{
-	struct binding	*b;
-	struct env	*e;
-
-	/* TODO: check for duplicates */
-
-	if (op->type != OP_LITERAL &&
-	    (op->type == OP_CAST && op->v.cast.expr->type != OP_LITERAL))
-		return 0;
-
-	b = xcalloc(1, sizeof(*b));
-	b->name = sym;
-
-	/* it's only a cast on a literal! */
-	if (op->type == OP_CAST) {
-		if (eval(op) != EVAL_OK) {
-			free(b);
-			return 0;
-		}
-		popv(&b->val);
-	} else
-		memcpy(&b->val, &op->v.literal, sizeof(b->val));
-
-	e = TAILQ_LAST(&envs, envs);
-	TAILQ_INSERT_HEAD(&e->bindings, b, entry);
-
-	return 1;
-}
-
-struct op *
-newop(int type)
-{
-	struct op *op;
-
-	op = xcalloc(1, sizeof(*op));
-	op->type = type;
-
-	return op;
-}
-
-void
-free_op_rec(struct op *op)
-{
-	struct op *n;
-
-	while (op != NULL) {
-		n = op->next;
-		free_op(op);
-		op = n;
-	}
-}
-
-void
-free_op(struct op *op)
-{
-	if (op == NULL)
-		return;
-
-	switch (op->type) {
-	case OP_REST:
-	case OP_LITERAL:
-	case OP_VARGS:
-		break;
-	case OP_ASSIGN:
-		free(op->v.assign.name);
-		free_op_rec(op->v.assign.expr);
-		break;
-	case OP_ASSERT:
-		free_op_rec(op->v.assert);
-		break;
-	case OP_FUNCALL:
-		free_op_rec(op->v.funcall.argv);
-		break;
-	case OP_VAR:
-		free(op->v.var);
-		break;
-	case OP_CAST:
-		free_op_rec(op->v.cast.expr);
-		break;
-	case OP_CMP_EQ:
-	case OP_CMP_LEQ:
-		free_op_rec(op->v.bin_cmp.a);
-		free_op_rec(op->v.bin_cmp.b);
-		break;
-	case OP_FACCESS:
-		free_op_rec(op->v.faccess.expr);
-		free(op->v.faccess.field);
-		break;
-	case OP_SFAIL:
-		free(op->v.sfail.msg);
-		free_op_rec(op->v.sfail.expr);
-		break;
-	default:
-		/* unreachable */
-		abort();
-	}
-
-	free(op);
-}
-
-struct op *
-op_rest(void)
-{
-	return newop(OP_REST);
-}
-
-struct op *
-op_assign(char *sym, struct op *expr)
-{
-	struct op *op;
-
-	op = newop(OP_ASSIGN);
-	op->v.assign.name = sym;
-	op->v.assign.expr = expr;
-
-	return op;
-}
-
-struct op *
-op_assert(struct op *expr)
-{
-	struct op *op;
-
-	op = newop(OP_ASSERT);
-	op->v.assert = expr;
-
-	return op;
-}
-
-struct op *
-op_var(char *sym)
-{
-	struct op *op;
-
-	op = newop(OP_VAR);
-	op->v.var = sym;
-
-	return op;
-}
-
-struct op *
-op_lit_str(char *str)
-{
-	struct op *op;
-
-	op = newop(OP_LITERAL);
-	op->v.literal.type = V_STR;
-	op->v.literal.v.str = str;
-
-	return op;
-}
-
-struct op *
-op_lit_num(uint64_t n)
-{
-	struct op *op;
-
-	op = newop(OP_LITERAL);
-	op->v.literal.type = V_NUM;
-	op->v.literal.v.num = n;
-
-	return op;
-}
-
-struct op *
-op_cmp_eq(struct op *a, struct op *b)
-{
-	struct op *op;
-
-	op = newop(OP_CMP_EQ);
-	op->v.bin_cmp.a = a;
-	op->v.bin_cmp.b = b;
-
-	return op;
-}
-
-struct op *
-op_cmp_leq(struct op *a, struct op *b)
-{
-	struct op *op;
-
-	op = newop(OP_CMP_LEQ);
-	op->v.bin_cmp.a = a;
-	op->v.bin_cmp.b = b;
-
-	return op;
-}
-
-struct op *
-op_cast(struct op *expr, int totype)
-{
-	struct op *op;
-
-	op = newop(OP_CAST);
-	op->v.cast.expr = expr;
-	op->v.cast.totype = totype;
-
-	return op;
-}
-
-struct op *
-op_faccess(struct op *expr, char *field)
-{
-	struct op *op;
-
-	op = newop(OP_FACCESS);
-	op->v.faccess.expr = expr;
-	op->v.faccess.field = field;
-
-	return op;
-}
-
-struct op *
-op_sfail(struct op *expr, char *msg)
-{
-	struct op	*op;
-
-	op = newop(OP_SFAIL);
-	op->v.sfail.expr = expr;
-	op->v.sfail.msg = msg;
-
-	return op;
-}
-
-struct op *
-op_vargs(void)
-{
-	struct op	*op;
-
-	op = newop(OP_VARGS);
-
-	return op;
-}
-
-void
-ppf_val(FILE *f, struct value *val)
-{
-	size_t	i;
-
-	switch (val->type) {
-	case V_SYM:
-		fprintf(f, "%s", val->v.str);
-		break;
-	case V_STR:
-		fprintf(f, "\"%s\"", val->v.str);
-		break;
-	case V_NUM:
-		fprintf(f, "%"PRIi64, val->v.num);
-		break;
-	case V_U8:
-		fprintf(f, "%"PRIu8, val->v.u8);
-		break;
-	case V_U16:
-		fprintf(f, "%"PRIu16, val->v.u16);
-		break;
-	case V_U32:
-		fprintf(f, "%"PRIu32, val->v.u32);
-		break;
-	case V_MSG:
-		fprintf(f, "(");
-		for (i = 0; i < val->v.msg.len; ++i)
-			fprintf(f, "%x%s", val->v.msg.msg[i],
-			    i == val->v.msg.len-1 ? "" : " ");
-		fprintf(f, ")");
-		break;
-	case V_QIDVEC:
-		fprintf(f, "qids[n=%zu]", val->v.qidvec.len);
-		break;
-	default:
-		fprintf(f, "<unknown value>");
-		break;
-	}
-}
-
-void
-pp_val(struct value *val)
-{
-	ppf_val(stdout, val);
-}
-
-const char *
-val_type(struct value *v)
-{
-	switch (v->type) {
-	case V_SYM: return "symbol";
-	case V_STR: return "string";
-	case V_NUM: return "number";
-	case V_MSG: return "message";
-	case V_QID: return "qid";
-	case V_U8: return "u8";
-	case V_U16: return "u16";
-	case V_U32: return "u32";
-	default: return "unknown";
-	}
-}
-
-int
-val_trueish(struct value *a)
-{
-	if (val_isnum(a))
-		return val_tonum(a);
-	return 1;
-}
-
-int
-val_isnum(struct value *a)
-{
-	return a->type == V_NUM
-		|| a->type == V_U8
-		|| a->type == V_U16
-		|| a->type == V_U32;
-}
-
-int64_t
-val_tonum(struct value *a)
-{
-	switch (a->type) {
-	case V_NUM: return a->v.num;
-	case V_U8:  return a->v.u8;
-	case V_U16: return a->v.u16;
-	case V_U32: return a->v.u32;
-	default:
-		before_printing();
-		fprintf(stderr, "%s: given value is not a number\n", __func__);
-		abort();
-	}
-}
-
-int
-val_eq(struct value *a, struct value *b)
-{
-	if (val_isnum(a) && val_isnum(b))
-		return val_tonum(a) == val_tonum(b);
-
-	if (a->type != b->type)
-		return 0;
-
-	switch (a->type) {
-	case V_STR:
-	case V_SYM:
-		return !strcmp(a->v.str, b->v.str);
-	}
-
-	return 0;
-}
-
-int
-val_leq(struct value *a, struct value *b)
-{
-	if (val_isnum(a) && val_isnum(b))
-		return val_tonum(a) <= val_tonum(b);
-	return 0;
-}
-
-static inline const char *
-pp_totype(int totype)
-{
-	/*
-	 * Not all of these are valid cast type thought, including
-	 * every possibility only to aid debugging.
-	 */
-	switch (totype) {
-	case V_STR: return "str";
-	case V_SYM: return "sym";
-	case V_NUM: return "num";
-	case V_QID: return "qid";
-	case V_U8:  return "u8";
-	case V_U16: return "u16";
-	case V_U32: return "u32";
-	default:    return "unknown";
-	}
-}
-
-int
-val_cast(struct value *a, int totype)
-{
-	int64_t v;
-
-#define NUMCAST(val, t, c, totype, max) do {				\
-		if (val > max) {					\
-			before_printing();				\
-			fprintf(stderr, "can't cast %"PRIu64		\
-			    " to %s\n", val, pp_totype(totype));	\
-			return EVAL_ERR;				\
-		}							\
-		a->type = totype;					\
-		a->v.t = (c)val;					\
-		return EVAL_OK;						\
-	} while (0)
-
-	if (a->type == totype)
-		return EVAL_OK;
-
-	if (!val_isnum(a)) {
-		before_printing();
-		fprintf(stderr, "can't cast ");
-		ppf_val(stderr, a);
-		fprintf(stderr, " to type %s\n", pp_totype(totype));
-		return EVAL_ERR;
-	}
-
-	v = a->v.num;
-	switch (totype) {
-	case V_U8:  NUMCAST(v, u8,  uint8_t,  totype, UINT8_MAX);
-	case V_U16: NUMCAST(v, u16, uint16_t, totype, UINT16_MAX);
-	case V_U32: NUMCAST(v, u32, uint32_t, totype, UINT32_MAX);
-	default:
-		before_printing();
-		fprintf(stderr, "can't cast %"PRIu64" to %s\n",
-		    v, pp_totype(totype));
-		return EVAL_ERR;
-	}
-
-#undef NUMCAST
-}
-
-int
-val_faccess(struct value *a, const char *field, struct value *ret)
-{
-	uint8_t		 mtype;
-	uint16_t	 len;
-	const char	*errstr;
-
-#define MSGTYPE(m) *(m.msg + 4)	/* skip the length */
-
-	switch (a->type) {
-	case V_QID:
-		/* TODO: add path.  needs uint64_t values thought! */
-		if (!strcmp(field, "vers")) {
-			ret->type = V_U32;
-			memcpy(&ret->v.u32, a->v.qid+1, 4);
-			return EVAL_OK;
-		} else if (!strcmp(field, "type")) {
-			ret->type = V_U8;
-			ret->v.u8 = *a->v.qid;
-			return EVAL_OK;
-		}
-		break;
-
-	case V_MSG:
-		mtype = MSGTYPE(a->v.msg);
-		if (!strcmp(field, "type")) {
-			ret->type = V_U8;
-			ret->v.u8 = MSGTYPE(a->v.msg);
-			return EVAL_OK;
-		} else if (!strcmp(field, "tag")) {
-			ret->type = V_U16;
-                        memcpy(&ret->v.u16, &a->v.msg.msg[5], 2);
-			ret->v.u16 = le16toh(ret->v.u16);
-			return EVAL_OK;
-		} else if (!strcmp(field, "msize") && mtype == Rversion) {
-			ret->type = V_U32;
-			memcpy(&ret->v.u32, &a->v.msg.msg[7], 4);
-			ret->v.u32 = le32toh(ret->v.u32);
-			return EVAL_OK;
-		} else if (!strcmp(field, "qid") && mtype == Rattach) {
-			ret->type = V_QID;
-			memcpy(&ret->v.qid, &a->v.msg.msg[7], QIDSIZE);
-			return EVAL_OK;
-		} else if (!strcmp(field, "nwqid") && mtype == Rwalk) {
-			ret->type = V_U16;
-			memcpy(&ret->v.u16, &a->v.msg.msg[7], 2);
-			ret->v.u16 = le16toh(ret->v.u16);
-			return EVAL_OK;
-		} else if (!strcmp(field, "wqid") && mtype == Rwalk) {
-			ret->type = V_QIDVEC;
-			ret->v.qidvec.start = &a->v.msg.msg[9];
-			memcpy(&len, &a->v.msg.msg[7], 2);
-			len = le16toh(len);
-			ret->v.qidvec.len = len;
-			return EVAL_OK;
-		}
-		break;
-
-	case V_QIDVEC:
-		len = strtonum(field, 0, MAXWELEM, &errstr);
-		if (errstr != NULL) {
-			before_printing();
-			printf("can't access qid #%s: %s\n", field, errstr);
-			return EVAL_ERR;
-		}
-
-		if (len >= a->v.qidvec.len) {
-			before_printing();
-			printf("can't access qid #%d: out-of-bound "
-			    "(max %zu)\n", len, a->v.qidvec.len);
-			return EVAL_ERR;
-		}
-
-		ret->type = V_QID;
-		memcpy(&ret->v.qid, a->v.qidvec.start + len * QIDSIZE,
-		    QIDSIZE);
-
-                return EVAL_OK;
-
-	default:
-		break;
-	}
-
-	before_printing();
-	printf("can't access field `%s' on type %s (", field, val_type(a));
-	pp_val(a);
-	printf(")\n");
-	return EVAL_ERR;
-
-#undef MSGTYPE
-}
-
-void
-pp_op(struct op *op)
-{
-	struct op	*aux;
-
-	switch (op->type) {
-	case OP_REST:
-		printf("...");
-		break;
-	case OP_ASSIGN:
-		printf("%s = ", op->v.assign.name);
-                pp_op(op->v.assign.expr);
-		break;
-	case OP_ASSERT:
-		printf("assert ");
-		pp_op(op->v.assert);
-		break;
-	case OP_FUNCALL:
-		printf("funcall %s(", op->v.funcall.proc->name);
-		for (aux = op->v.funcall.argv; aux != NULL; aux = aux->next) {
-			pp_op(aux);
-			if (aux->next != NULL)
-				printf(", ");
-		}
-		printf(")");
-		break;
-	case OP_LITERAL:
-                pp_val(&op->v.literal);
-		break;
-	case OP_VAR:
-		printf("%s", op->v.var);
-		break;
-	case OP_CAST:
-		pp_op(op->v.cast.expr);
-		printf(":");
-		switch (op->v.cast.totype) {
-		case V_U8: printf("u8"); break;
-		case V_U16: printf("u16"); break;
-		case V_U32: printf("u32"); break;
-		case V_STR: printf("str"); break;
-		default: printf("???"); break;
-		}
-		break;
-	case OP_CMP_EQ:
-		pp_op(op->v.bin_cmp.a);
-		printf(" == ");
-		pp_op(op->v.bin_cmp.b);
-		break;
-	case OP_CMP_LEQ:
-		pp_op(op->v.bin_cmp.a);
-		printf(" <= ");
-		pp_op(op->v.bin_cmp.b);
-		break;
-	case OP_FACCESS:
-		pp_op(op->v.faccess.expr);
-		printf(".%s", op->v.faccess.field);
-		break;
-	case OP_SFAIL:
-		printf("should-fail ");
-		pp_op(op->v.sfail.expr);
-		if (op->v.sfail.msg != NULL)
-			printf(": \"%s\"", op->v.sfail.msg);
-		break;
-	case OP_VARGS:
-		printf("vargs");
-		break;
-	default:
-		printf(" ???[%d] ", op->type);
-	}
-}
-
-void
-pp_block(struct op *op)
-{
-        while (op != NULL) {
-		printf("> ");
-		pp_op(op);
-		printf("\n");
-
-		op = op->next;
-	}
-}
-
-int
-eval(struct op *op)
-{
-	struct value	 a, b;
-	struct proc	*proc;
-	struct op	*t, *tnext;
-	int		 i, ret;
-
-#if DEBUG
-	pp_op(op);
-	printf("\n");
-#endif
-
-	switch (op->type) {
-	case OP_REST:
-		/*
-		 * Try to load the rest argument.  Note that it can be
-		 * empty!
-		 */
-                if ((ret = getvar_raw("...", &t)) == EVAL_OK)
-			if ((ret = eval(t)) != EVAL_OK)
-				return ret;
-		break;
-
-	case OP_ASSIGN:
-		ret = setvar(op->v.assign.name, op->v.assign.expr);
-		if (ret != EVAL_OK)
-			return ret;
-		break;
-
-	case OP_ASSERT:
-		if ((ret = eval(op->v.assert)) != EVAL_OK)
-			return ret;
-                popv(&a);
-                if (!val_trueish(&a)) {
-			before_printing();
-			printf("assertion failed: ");
-			pp_op(op->v.assert);
-			printf("\n");
-			return EVAL_ERR;
-		}
-		break;
-
-	case OP_FUNCALL:
-		/* assume airity matches */
-
-		proc = op->v.funcall.proc;
-		if (proc->nativefn != NULL) {
-			/*
-			 * Push arguments on the stack for builtin
-			 * functions.  Counting the height of the
-			 * stack is done to compute the correct number
-			 * in the vararg case.  argc only counts the
-			 * "syntactical" arguments, i.e. foo(x, ...)
-			 * has argc == 2, but at runtime argc may be
-			 * 1, 2 or a greater number!
-			 */
-
-			i = stackh;
-			t = op->v.funcall.argv;
-			if (t != NULL && (ret = eval(t)) != EVAL_OK)
-				return ret;
-			i = stackh - i;
-
-			assert(i >= 0);
-
-			if ((ret = proc->nativefn(i))
-			    != EVAL_OK)
-				return ret;
-		} else {
-			if (proc->body == NULL) {
-				before_printing();
-				printf("warn: calling the empty proc `%s'\n",
-				    proc->name);
-				break;
-			}
-
-			pushenv();
-
-			for (t = op->v.funcall.argv, i = 0;
-			     t != NULL;
-			     t = t->next, i++) {
-				/*
-				 * Push a pseudo variable `...' (and
-				 * don't evaluate it) in the vararg
-				 * case.  A special case is when the
-				 * variable is itself `...'.
-				 */
-				if (proc->vararg && i == proc->minargs) {
-					if (t->type != OP_REST)
-						setvar_raw(xstrdup("..."), t);
-					break;
-				}
-
-				/*
-				 * The arguments are a linked list of
-				 * ops.  Setvar will call eval that
-				 * will evaluate *all* the arguments.
-				 * The dance here that sets next to
-				 * NULL and then restores it is to
-				 * avoid this behaviour.
-				 */
-				tnext = t->next;
-				t->next = NULL;
-				ret = setvar(proc->args[i], t);
-				t->next = tnext;
-
-				if (ret != EVAL_OK)
-					return ret;
-			}
-
-			if ((ret = eval(proc->body)) != EVAL_OK)
-				return ret;
-
-			popenv();
-		}
-
-		break;
-
-	case OP_LITERAL:
-		pushv(&op->v.literal);
-		break;
-
-	case OP_VAR:
-                if ((ret = getvar(op->v.var, &a)) != EVAL_OK)
-			return ret;
-		pushv(&a);
-		break;
-
-	case OP_CAST:
-		if ((ret = eval(op->v.cast.expr)) != EVAL_OK)
-			return ret;
-		popv(&a);
-		if ((ret = val_cast(&a, op->v.cast.totype)) != EVAL_OK)
-			return ret;
-		pushv(&a);
-		break;
-
-	case OP_CMP_EQ:
-		if ((ret = eval(op->v.bin_cmp.a)) != EVAL_OK)
-			return ret;
-		if ((ret = eval(op->v.bin_cmp.b)) != EVAL_OK)
-			return ret;
-
-		popv(&b);
-		popv(&a);
-		pushbool(val_eq(&a, &b));
-		break;
-
-	case OP_CMP_LEQ:
-		if ((ret = eval(op->v.bin_cmp.a)) != EVAL_OK)
-			return ret;
-		if ((ret = eval(op->v.bin_cmp.b)) != EVAL_OK)
-			return ret;
-
-		popv(&b);
-		popv(&a);
-		pushbool(val_leq(&a, &b));
-		break;
-
-	case OP_FACCESS:
-		if ((ret = eval(op->v.faccess.expr)) != EVAL_OK)
-			return ret;
-		popv(&a);
-		if ((ret = val_faccess(&a, op->v.faccess.field, &b))
-		    != EVAL_OK)
-			return ret;
-		pushv(&b);
-		break;
-
-	case OP_SFAIL:
-		if ((ret = eval(op->v.sfail.expr)) == EVAL_OK) {
-			before_printing();
-			printf("expecting failure");
-			if (op->v.sfail.msg != NULL)
-				printf(" \"%s\"", op->v.sfail.msg);
-			printf("\n");
-			printf("expression: ");
-			pp_op(op->v.sfail.expr);
-			printf("\n");
-			return EVAL_ERR;
-		}
-		if (ret == EVAL_SKIP)
-			return ret;
-		break;
-
-	case OP_VARGS:
-		if ((ret = getvar_raw("...", &t)) == EVAL_OK) {
-                        for (i = 0; t != NULL; t = t->next)
-				i++;
-			pushnum(i);
-		} else
-			pushnum(0);
-		break;
-
-	default:
-		before_printing();
-		fprintf(stderr, "invalid op, aborting.\n");
-		abort();
-	}
-
-	if (op->next)
-		return eval(op->next);
-	return EVAL_OK;
-}
-
-void
-prepare_funcall(void)
-{
-	pushstack(&args);
-}
-
-void
-push_arg(struct op *op)
-{
-	push(&args, op);
-}
-
-struct op *
-op_funcall(struct proc *proc)
-{
-	struct op	*op, *argv;
-	int		 argc;
-
-	argv = finalize(&args, &argc);
-
-	op = newop(OP_FUNCALL);
-	op->v.funcall.proc = proc;
-	op->v.funcall.argv = argv;
-	op->v.funcall.argc = argc;
-
-	return op;
-}
-
-void
-add_builtin_proc(const char *name, int (*fn)(int), int argc, int vararg)
-{
-	struct proc *proc;
-
-	proc = xcalloc(1, sizeof(*proc));
-	proc->name = xstrdup(name);
-	proc->nativefn = fn;
-	proc->minargs = argc;
-	proc->vararg = vararg;
-
-	TAILQ_INSERT_HEAD(&procs, proc, entry);
-}
-
-void
-prepare_proc(void)
-{
-	pushstack(&args);
-}
-
-int
-proc_setup_body(void)
-{
-	struct opstack	*argv;
-	struct op	*op;
-	int		 i;
-
-	argv = peek(&args);
-	for (i = 0, op = argv->base.next; op != NULL; i++) {
-		/*
-		 * TODO: should free the whole list on error but..,
-		 * we're gonna exit real soon(tm)!
-		 */
-		if (op->type != OP_VAR && op->type != OP_REST)
-			return 0;
-
-		op = op->next;
-	}
-
-	assert(i == argv->counter);
-	pushstack(&blocks);
-	return 1;
-}
-
-void
-proc_done(char *name)
-{
-	struct proc	*proc;
-	struct op	*op, *next, *argv, *body;
-	int		 i, argc;
-
-	argv = finalize(&args, &argc);
-	body = finalize(&blocks, NULL);
-
-	proc = xcalloc(1, sizeof(*proc));
-	proc->name = name;
-	proc->minargs = argc;
-
-        for (i = 0, op = argv; op != NULL; ++i) {
-		if (op->type == OP_REST) {
-			proc->vararg = 1;
-			proc->minargs = i;
-			break;
-		}
-
-		proc->args[i] = xstrdup(op->v.var);
-
-		next = op->next;
-		free_op(op);
-		op = next;
-	}
-	assert(i == argc || (proc->vararg && i == proc->minargs));
-
-	proc->body = body;
-
-	TAILQ_INSERT_HEAD(&procs, proc, entry);
-}
-
-void
-block_push(struct op *op)
-{
-	push(&blocks, op);
-}
-
-struct proc *
-proc_by_name(const char *name)
-{
-	struct proc *p;
-
-	TAILQ_FOREACH(p, &procs, entry) {
-		if (!strcmp(p->name, name))
-			return p;
-	}
-
-	return NULL;
-}
-
-void
-prepare_test(void)
-{
-	pushstack(&blocks);
-}
-
-void
-test_done(int shouldfail, char *name, char *dir)
-{
-	struct test	*test;
-
-	test = xcalloc(1, sizeof(*test));
-	test->shouldfail = shouldfail;
-	test->name = name;
-	test->dir = dir;
-	test->body = finalize(&blocks, NULL);
-
-	if (TAILQ_EMPTY(&tests))
-		TAILQ_INSERT_HEAD(&tests, test, entry);
-	else
-		TAILQ_INSERT_TAIL(&tests, test, entry);
-
-	ntests++;
-}
-
-static int
-builtin_print(int argc)
-{
-	struct value	v;
-	int		i;
-
-	before_printing();
-
-	for (i = argc; i > 0; --i) {
-		peekn(i, &v);
-		if (v.type == V_STR)
-			printf("%s", v.v.str);
-		else
-			pp_val(&v);
-		printf(" ");
-	}
-
-	printf("\n");
-
-	popvn(argc);
-
-	return EVAL_OK;
-}
-
-static int
-builtin_debug(int argc)
-{
-	if (debug)
-		return builtin_print(argc);
-
-	popvn(argc);
-	return EVAL_OK;
-}
-
-static int
-builtin_skip(int argc)
-{
-	return EVAL_SKIP;
-}
-
-static int
-builtin_iota(int argc)
-{
-	struct value v;
-
-	v.type = V_U16;
-	if ((v.v.u16 = ++lasttag) == 255)
-		v.v.u16 = ++lasttag;
-
-	pushv(&v);
-	return EVAL_OK;
-}
-
-static int
-builtin_send(int argc)
-{
-	struct ibuf	*buf;
-	struct value	 v;
-	uint32_t	 len;
-	uint16_t	 slen;
-	int		 i;
-
-	check_for_output();
-
-	/*
-	 * Compute the length of the packet.  4 is for the initial
-	 * length field
-	 */
-	len = 4;
-
-	for (i = argc; i > 0; --i) {
-		peekn(i, &v);
-		switch (v.type) {
-		case V_STR:
-			len += 2; /* count */
-			len += strlen(v.v.str);
-			break;
-
-		case V_U8:
-			len += 1;
-			break;
-
-		case V_U16:
-			len += 2;
-			break;
-
-		case V_U32:
-			len += 4;
-			break;
-
-		default:
-			before_printing();
-			printf("%s: can't serialize ", __func__);
-			pp_val(&v);
-			printf("\n");
-			return EVAL_ERR;
-		}
-	}
-
-	if (len > UINT16_MAX) {
-		before_printing();
-		printf("%s: message size too long: got %d when max is %d\n",
-		    __func__, len, UINT16_MAX);
-		return EVAL_ERR;
-	}
-
-	if ((buf = imsg_create(&ibuf, IMSG_BUF, 0, 0, len)) == NULL)
-		fatal("imsg_create(%d)", len);
-
-	len = htole32(len);
-	imsg_add(buf, &len, sizeof(len));
-
-	for (i = argc; i > 0; --i) {
-		peekn(i, &v);
-		switch (v.type) {
-		case V_STR:
-			slen = strlen(v.v.str);
-			slen = htole16(slen);
-			imsg_add(buf, &slen, sizeof(slen));
-			imsg_add(buf, v.v.str, strlen(v.v.str));
-			break;
-
-		case V_U8:
-			imsg_add(buf, &v.v.u8, 1);
-			break;
-
-		case V_U16:
-			v.v.u16 = htole16(v.v.u16);
-			imsg_add(buf, &v.v.u16, 2);
-			break;
-
-		case V_U32:
-			v.v.u32 = htole32(v.v.u32);
-			imsg_add(buf, &v.v.u32, 4);
-			break;
-		}
-	}
-
-	imsg_close(&ibuf, buf);
-
-	if (imsg_flush(&ibuf) == -1) {
-		i = errno;
-		before_printing();
-		printf("%s: imsg_flush failed: %s\n", __func__, strerror(i));
-		return EVAL_ERR;
-	}
-
-	check_for_output();
-	return EVAL_OK;
-}
-
-static int
-builtin_recv(int argc)
-{
-	struct pollfd	pfd;
-        struct value	v;
-	struct imsg	imsg;
-	ssize_t		n, datalen;
-	int		serrno;
-
-	if (lastmsg != NULL) {
-		free(lastmsg);
-		lastmsg = NULL;
-	}
-
-	pfd.fd = ibuf.fd;
-	pfd.events = POLLIN;
-	if (poll(&pfd, 1, INFTIM) == -1) {
-		serrno = errno;
-		before_printing();
-		printf("%s: poll failed: %s\n", __func__, strerror(serrno));
-		return EVAL_ERR;
-	}
-
-again:
-	if ((n = imsg_read(&ibuf)) == -1) {
-		if (errno == EAGAIN)
-			goto again;
-		fatal("imsg_read");
-	}
-	if (n == 0) {
-disconnect:
-		before_printing();
-		printf("child disconnected\n");
-		return EVAL_ERR;
-	}
-
-nextmessage:
-	check_for_output();
-
-	/* read only one message */
-	if ((n = imsg_get(&ibuf, &imsg)) == -1)
-		fatal("imsg_get");
-	if (n == 0)
-		goto disconnect;
-
-	datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
-	switch (imsg.hdr.type) {
-	case IMSG_BUF:
-		v.type = V_MSG;
-		if ((v.v.msg.msg = malloc(datalen)) == NULL)
-			fatal("malloc");
-		memcpy(v.v.msg.msg, imsg.data, datalen);
-		v.v.msg.len = datalen;
-		pushv(&v);
-		imsg_free(&imsg);
-                return EVAL_OK;
-
-	case IMSG_CLOSE:
-		before_printing();
-		printf("subprocess closed the connection\n");
-		imsg_free(&imsg);
-		return EVAL_ERR;
-
-	case IMSG_MSIZE:
-		imsg_free(&imsg);
-		goto nextmessage;
-
-	default:
-		before_printing();
-		printf("got unknown message from subprocess: %d\n",
-		    imsg.hdr.type);
-		imsg_free(&imsg);
-		return EVAL_ERR;
-	}
-}
-
-static pid_t
-spawn_client_proc(void)
-{
-	const char	*argv[4];
-	int		 p[2], out[2], argc = 0;
-	pid_t		 pid;
-
-	if (child_out != -1)
-		close(child_out);
-
-	if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
-	    PF_UNSPEC, p) == -1)
-		fatal("socketpair");
-
-	if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
-	    PF_UNSPEC, out) == -1)
-		fatal("socketpair");
-
-	switch (pid = fork()) {
-	case -1:
-		fatal("cannot fork");
-	case 0:
-		break;
-	default:
-		close(p[1]);
-		close(out[1]);
-		child_out = out[0];
-		if (ibuf_inuse) {
-			msgbuf_clear(&ibuf.w);
-			close(ibuf.fd);
-		}
-		imsg_init(&ibuf, p[0]);
-		ibuf_inuse = 1;
-		return pid;
-	}
-
-	close(p[0]);
-	close(out[0]);
-
-	if (dup2(out[1], 1) == -1 ||
-	    dup2(out[1], 2) == -1)
-		fatal("dup2");
-
-	if (p[1] != 3) {
-		if (dup2(p[1], 3) == -1)
-			fatal("cannot setup imsg fd");
-	} else if (fcntl(F_SETFD, 0) == -1)
-		fatal("cannot setup imsg fd");
-
-	argv[argc++] = argv0;
-	argv[argc++] = "-Tc";
-
-#if DEBUG
-	argv[argc++] = "-v";
-#endif
-
-	argv[argc++] = NULL;
-
-	execvp(argv0, (char *const *)argv);
-	fatal("execvp");
-}
-
-static void
-prepare_child_for_test(struct test *t)
-{
-	struct passwd	*pw;
-	struct stat	 sb;
-
-	if (stat(t->dir, &sb) == -1)
-		fatal("stat(\"%s\")", t->dir);
-
-	if ((pw = getpwuid(sb.st_uid)) == NULL)
-		fatal("getpwuid(%d)", sb.st_uid);
-
-	imsg_compose(&ibuf, IMSG_AUTH, 0, 0, -1,
-	    pw->pw_name, strlen(pw->pw_name)+1);
-	imsg_compose(&ibuf, IMSG_AUTH_DIR, 0, 0, -1,
-	    t->dir, strlen(t->dir)+1);
-
-	if (imsg_flush(&ibuf) == -1)
-		fatal("imsg_flush");
-}
-
-static int
-run_test(struct test *t)
-{
-	pid_t	pid;
-	int	ret;
-
-#if DEBUG
-	before_printing();
-	puts("=====================");
-	pp_block(t->body);
-	puts("=====================");
-#endif
-
-        if (stackh != 0)
-		popvn(stackh);
-
-	if (t->body == NULL) {
-		before_printing();
-		printf("no instructions, skipping...\n");
-		return EVAL_SKIP;
-	}
-
-	pid = spawn_client_proc();
-        prepare_child_for_test(t);
-	ret = eval(t->body);
-
-	imsg_compose(&ibuf, IMSG_CONN_GONE, 0, 0, -1, NULL, 0);
-	imsg_flush(&ibuf);
-
-	while (waitpid(pid, NULL, 0) != pid)
-		; /* nop */
-
-	check_for_output();
-
-	if (t->shouldfail) {
-		if (ret == EVAL_OK) {
-			before_printing();
-			printf("test was expected to fail\n");
-			return EVAL_ERR;
-		} else if (ret == EVAL_ERR)
-			return EVAL_OK;
-	}
-
-	return ret;
-}
-
-int
-main(int argc, char **argv)
-{
-	struct test	*t;
-	int		 ch, i, r, passed = 0, failed = 0, skipped = 0;
-	int		 runclient = 0;
-	const char	*pat = NULL;
-	regex_t		 reg;
-
-	assert(argv0 = argv[0]);
-
-	signal(SIGPIPE, SIG_IGN);
-
-	log_init(1, LOG_DAEMON);
-	log_setverbose(1);
-
-	/* prepare the global env */
-	pushenv();
-
-	add_builtin_proc("print", builtin_print, 1, 1);
-	add_builtin_proc("debug", builtin_debug, 1, 1);
-	add_builtin_proc("skip", builtin_skip, 0, 0);
-	add_builtin_proc("iota", builtin_iota, 0, 0);
-	add_builtin_proc("send", builtin_send, 2, 1);
-	add_builtin_proc("recv", builtin_recv, 0, 0);
-
-	while ((ch = getopt(argc, argv, "nT:vx:")) != -1) {
-		switch (ch) {
-		case 'n':
-			syntaxcheck = 1;
-			break;
-		case 'T':
-			assert(*optarg == 'c');
-                        runclient = 1;
-			break;
-		case 'v':
-			debug = 1;
-			break;
-		case 'x':
-			pat = optarg;
-			break;
-		default:
-			fprintf(stderr, "Usage: %s [-nv] [files...]\n",
-			    *argv);
-			exit(1);
-		}
-	}
-	argc -= optind;
-	argv += optind;
-
-	if (runclient)
-		client(1, debug);
-
-	if (pat == NULL)
-		pat = ".*";
-
-	if (regcomp(&reg, pat, REG_BASIC | REG_ICASE | REG_NOSUB) != 0)
-		fatalx("invalid regexp: %s", pat);
-
-	for (i = 0; i < argc; ++i)
-		loadfile(argv[i]);
-
-	if (syntaxcheck) {
-		fprintf(stderr, "files OK\n");
-		return 0;
-	}
-
-	/* Check for root privileges. */
-        if (geteuid())
-                fatalx("need root privileges");
-
-	i = 0;
-	TAILQ_FOREACH(t, &tests, entry) {
-		if (regexec(&reg, t->name, 0, NULL, 0) != 0)
-			continue;
-
-		printf("===> [%d/%d] running test \"%s\"... ", i+1, ntests,
-		    t->name);
-		fflush(stdout);
-
-		filler = "\n";
-		r = run_test(t);
-		if (filler == NULL)
-			printf("=> test ");
-
-		switch (r) {
-		case EVAL_OK:
-			printf("passed\n");
-			passed++;
-			break;
-		case EVAL_ERR:
-			failed++;
-			printf("failed\n");
-			break;
-		case EVAL_SKIP:
-			printf("skipped\n");
-			skipped++;
-			break;
-		}
-
-		if (filler == NULL)
-			printf("\n");
-		i++;
-	}
-
-	printf("\n");
-	printf("%d/%d passed (%d skipped and %d failed)\n",
-	    passed, i, skipped, failed);
-
-	popenv();
-	free(lastmsg);
-	regfree(&reg);
-
-	return failed != 0;
-}
blob - f7b6a07ae2b5f8ecc35bea64130cf0b6af2af2c0 (mode 644)
blob + /dev/null
--- script.h
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef SCRIPT_H
-#define SCRIPT_H
-
-#include "compat.h"
-
-#include <stdio.h>
-
-#include "kamid.h"
-
-enum {
-	/* literals */
-	V_SYM,
-	V_STR,
-	V_NUM,
-
-	/* foreign */
-	V_MSG,
-	V_QIDVEC,
-	V_QID,
-
-	/* casted */
-	V_U8,
-	V_U16,
-	V_U32,
-};
-
-struct value {
-	int type;
-	union {
-		char		*str;
-		int64_t		 num;
-		uint8_t		 u8;
-		uint16_t	 u16;
-		uint32_t	 u32;
-		struct {
-			uint8_t	*msg;
-			size_t	 len;
-		} msg;
-		struct {
-			uint8_t	*start;
-			size_t	 len;
-		} qidvec;
-		uint8_t		 qid[QIDSIZE];
-	} v;
-};
-
-enum {
-	OP_REST,
-	OP_ASSIGN,
-	OP_ASSERT,
-	OP_FUNCALL,
-	OP_LITERAL,
-	OP_VAR,
-	OP_CAST,
-	OP_CMP_EQ,
-	OP_CMP_LEQ,
-	OP_FACCESS,
-	OP_SFAIL,
-	OP_VARGS,
-};
-
-struct proc;
-
-struct op {
-	struct op	*next;
-	int		 type;
-	union {
-		struct {
-			char		*name;
-			struct op	*expr;
-		} assign;
-		struct op		*assert;
-		struct {
-			struct proc	*proc;
-			struct op	*argv;
-			int		 argc;
-		} funcall;
-		struct value literal;
-		char *var;
-		struct {
-			struct op	*expr;
-			int		 totype;
-		} cast;
-		struct {
-			struct op	*a;
-			struct op	*b;
-		} bin_cmp;
-		struct {
-			struct op	*expr;
-			char		*field;
-		} faccess;
-		struct {
-			char		*msg;
-			struct op	*expr;
-		} sfail;
-	} v;
-};
-
-TAILQ_HEAD(bindings, binding);
-struct binding {
-	TAILQ_ENTRY(binding)	 entry;
-	char			*name;
-	struct value		 val;
-
-	/*
-	 * Hack to support varargs.  We set a special variable named
-	 * "..." that contains the list of ops that will evaluate to
-	 * the arguments.
-	 */
-	struct op		*raw;
-};
-
-TAILQ_HEAD(envs, env);
-struct env {
-	TAILQ_ENTRY(env)	 entry;
-	struct bindings		 bindings;
-};
-
-TAILQ_HEAD(opstacks, opstack);
-struct opstack {
-	TAILQ_ENTRY(opstack)	 entry;
-	struct op		 base;
-	struct op		*last;
-	int			 counter;
-};
-
-TAILQ_HEAD(procs, proc);
-struct proc {
-	TAILQ_ENTRY(proc)	 entry;
-	char			*name;
-	int			 minargs;
-	int			 vararg;
-	char			*args[MAXWELEM];
-	struct op		*body;
-	int			(*nativefn)(int);
-};
-
-TAILQ_HEAD(tests, test);
-struct test {
-	TAILQ_ENTRY(test)	 entry;
-	int			 shouldfail;
-	char			*name;
-	char			*dir;
-	struct op		*body;
-};
-
-enum {
-	EVAL_OK,
-	EVAL_ERR,
-	EVAL_SKIP,
-};
-
-int		 global_set(char *, struct op *);
-
-struct op	*newop(int);
-void		 free_op_rec(struct op *);
-void		 free_op(struct op *);
-struct op	*op_rest(void);
-struct op	*op_assign(char *, struct op *);
-struct op	*op_assert(struct op *);
-struct op	*op_var(char *);
-struct op	*op_lit_str(char *);
-struct op	*op_lit_num(uint64_t);
-struct op	*op_cmp_eq(struct op *, struct op *);
-struct op	*op_cmp_leq(struct op *, struct op *);
-struct op	*op_cast(struct op *, int);
-struct op	*op_faccess(struct op *, char *);
-struct op	*op_sfail(struct op *, char *);
-struct op	*op_vargs(void);
-
-void		 ppf_val(FILE *, struct value *);
-void		 pp_val(struct value *);
-void		 pp_val(struct value *);
-const char	*val_type(struct value *);
-int		 val_trueish(struct value *);
-int		 val_isnum(struct value *);
-int64_t		 val_tonum(struct value *);
-int		 val_eq(struct value *, struct value *);
-int		 val_leq(struct value *, struct value *);
-int		 val_cast(struct value *, int);
-int		 val_faccess(struct value *, const char *, struct value *);
-void		 pp_op(struct op *);
-void		 pp_block(struct op *);
-int		 eval(struct op *);
-
-/* funcall */
-void		 prepare_funcall(void);
-void		 push_arg(struct op *);
-struct op	*op_funcall(struct proc *);
-
-/* proc */
-void		 add_builtin_proc(const char *name, int (*)(int), int, int);
-void		 prepare_proc(void);
-/* push_arg works on procs too */
-int		 proc_setup_body(void);
-void		 proc_done(char *name);
-void		 block_push(struct op *);
-struct proc	*proc_by_name(const char *);
-
-/* testing */
-void		 prepare_test(void);
-void		 test_done(int, char *, char *);
-
-/* np.y */
-void		 loadfile(const char *);
-
-#endif
blob - 801209c5402fa8ad9a51028d4aa0e06848279093 (mode 644)
blob + /dev/null
--- table.c
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * 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 "compat.h"
-
-#include <stdlib.h>
-#include <string.h>
-
-#include "log.h"
-#include "table.h"
-#include "utils.h"
-
-int
-table_open(struct kd_conf *conf, const char *name, const char *type,
-    const char *path)
-{
-	struct table *t;
-	struct kd_tables_conf *entry;
-	struct table_backend *backends[] = {
-		&table_static,
-		NULL,
-	}, *b;
-	size_t i;
-
-	for (i = 0; backends[i] != NULL; ++i) {
-		b = backends[i];
-		if (!strcmp(type, b->name))
-			goto found;
-	}
-	log_warn("unknown table type %s", type);
-	return -1;
-
-found:
-	if (b->open == NULL) {
-		log_warn("can't open table %s (type %s)",
-		    name, b->name);
-		return -1;
-	}
-
-	t = xcalloc(1, sizeof(*t));
-	strlcpy(t->t_name, name, sizeof(t->t_name));
-	if (path != NULL)
-		strlcpy(t->t_path, path, sizeof(t->t_path));
-	t->t_backend = b;
-
-	if (t->t_backend->open(t) == -1)
-		fatal("can't open table %s (type %s)",
-		    name, path);
-
-	entry = xcalloc(1, sizeof(*entry));
-	entry->table = t;
-	STAILQ_INSERT_HEAD(&conf->table_head, entry, entry);
-	return 0;
-}
-
-int
-table_add(struct table *t, const char *key, const char *val)
-{
-	if (t->t_backend->add == NULL) {
-		log_warn("can't add to table %s (type %s)",
-		    t->t_name, t->t_backend->name);
-		return -1;
-	}
-
-	return t->t_backend->add(t, key, val);
-}
-
-int
-table_lookup(struct table *t, const char *key, char **ret_val)
-{
-	if (t->t_backend->lookup == NULL) {
-		log_warn("can't lookup table %s (type %s)",
-		    t->t_name, t->t_backend->name);
-		return -1;
-	}
-
-	return t->t_backend->lookup(t, key, ret_val);
-}
-
-void
-table_close(struct table *t)
-{
-	if (t->t_backend->close != NULL)
-		t->t_backend->close(t);
-}
blob - 5380d45279b042b87c616571f4f34c053c0cf217 (mode 644)
blob + /dev/null
--- table.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef TABLE_H
-#define TABLE_H
-
-#include "kamid.h"
-
-int	 table_open(struct kd_conf *, const char *, const char *, const char *);
-int	 table_add(struct table *, const char *, const char *);
-int	 table_lookup(struct table *, const char *, char **);
-void	 table_close(struct table *);
-
-#endif
blob - cb42223006577b8071076b8753890c9fe56df50e (mode 644)
blob + /dev/null
--- table_static.c
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * 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 "compat.h"
-
-#include <stdlib.h>
-#include <string.h>
-
-#include "kamid.h"
-#include "utils.h"
-
-static void	*hash_alloc(size_t, void *);
-static void	*hash_calloc(size_t, size_t, void *);
-static void	 hash_free(void *, void *);
-
-static int	 table_static_open(struct table *);
-static int	 table_static_add(struct table *, const char *, const char *);
-static int	 table_static_lookup(struct table *, const char *, char **);
-static void	 table_static_close(struct table *);
-
-struct table_backend table_static = {
-	"static",
-	table_static_open,
-	table_static_add,
-	table_static_lookup,
-	table_static_close,
-};
-
-struct kp {
-	char	*val;
-	char	 key[];
-};
-
-static void *
-hash_alloc(size_t len, void *d)
-{
-	return xmalloc(len);
-}
-
-static void *
-hash_calloc(size_t nmemb, size_t size, void *d)
-{
-	return xcalloc(nmemb, size);
-}
-
-static void
-hash_free(void *ptr, void *d)
-{
-	free(ptr);
-}
-
-static int
-table_static_open(struct table *t)
-{
-	struct ohash_info info = {
-		.key_offset = offsetof(struct kp, key),
-		.calloc = hash_calloc,
-		.free = hash_free,
-		.alloc = hash_alloc,
-	};
-
-	t->t_handle = xmalloc(sizeof(struct ohash));
-	ohash_init(t->t_handle, 5, &info);
-	return 0;
-}
-
-int
-table_static_add(struct table *t, const char *key, const char *val)
-{
-	struct kp	*kp;
-	unsigned int	 slot;
-
-	if (key == NULL)
-		return -1;
-
-	kp = xcalloc(1, sizeof(*kp) + strlen(key) + 1);
-	strcpy(kp->key, key);
-	if (val != NULL)
-		kp->val = xstrdup(val);
-
-	slot = ohash_qlookup(t->t_handle, kp->key);
-	ohash_insert(t->t_handle, slot, kp);
-
-	return 0;
-}
-
-int
-table_static_lookup(struct table *t, const char *key, char **ret_val)
-{
-	struct kp	*kp;
-	unsigned int	 slot;
-
-	slot = ohash_qlookup(t->t_handle, key);
-	if ((kp = ohash_find(t->t_handle, slot)) == NULL)
-		return -1;
-
-	*ret_val = xstrdup(kp->val);
-	return 0;
-}
-
-static void
-table_static_close(struct table *t)
-{
-	struct kp	*kp;
-	unsigned int	 i;
-
-	for (kp = ohash_first(t->t_handle, &i);
-	     kp != NULL;
-	     kp = ohash_next(t->t_handle, &i)) {
-		ohash_remove(t->t_handle, i);
-		free(kp->key);
-		free(kp->val);
-		free(kp);
-	}
-
-	free(t->t_handle);
-}
blob - 3315f4365f5644798ab3fc243eccdc05c228926f (mode 644)
blob + /dev/null
--- utils.c
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * 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 "compat.h"
-
-#include <ctype.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "kamid.h"
-#include "log.h"
-#include "utils.h"
-
-void *
-xmalloc(size_t size)
-{
-	void	*r;
-
-	if ((r = malloc(size)) == NULL)
-		fatal("malloc");
-	return r;
-}
-
-void *
-xcalloc(size_t nmemb, size_t size)
-{
-	void	*r;
-
-	if ((r = calloc(nmemb, size)) == NULL)
-		fatal("calloc");
-	return r;
-}
-
-char *
-xstrdup(const char *s)
-{
-	char	*r;
-
-	if ((r = strdup(s)) == NULL)
-		fatal("strdup");
-	return r;
-}
-
-void *
-xmemdup(const void *d, size_t len)
-{
-	void	*r;
-
-	if ((r = malloc(len)) == NULL)
-		fatal("malloc");
-	memcpy(r, d, len);
-	return r;
-}
-
-const char *
-pp_msg_type(uint8_t type)
-{
-	switch (type) {
-	case Tversion:	return "Tversion";
-	case Rversion:	return "Rversion";
-	case Tauth:	return "Tauth";
-	case Rauth:	return "Rauth";
-	case Tattach:	return "Tattach";
-	case Rattach:	return "Rattach";
-	case Terror:	return "Terror"; /* illegal */
-	case Rerror:	return "Rerror";
-	case Tflush:	return "Tflush";
-	case Rflush:	return "Rflush";
-	case Twalk:	return "Twalk";
-	case Rwalk:	return "Rwalk";
-	case Topen:	return "Topen";
-	case Ropen:	return "Ropen";
-	case Tcreate:	return "Tcreate";
-	case Rcreate:	return "Rcreate";
-	case Tread:	return "Tread";
-	case Rread:	return "Rread";
-	case Twrite:	return "Twrite";
-	case Rwrite:	return "Rwrite";
-	case Tclunk:	return "Tclunk";
-	case Rclunk:	return "Rclunk";
-	case Tremove:	return "Tremove";
-	case Rremove:	return "Rremove";
-	case Tstat:	return "Tstat";
-	case Rstat:	return "Rstat";
-	case Twstat:	return "Twstat";
-	case Rwstat:	return "Rwstat";
-	default:	return "unknown";
-	}
-}
-
-const char *
-pp_qid_type(uint8_t type)
-{
-	switch (type) {
-	case QTDIR:     return "dir";
-	case QTAPPEND:  return "append-only";
-	case QTEXCL:    return "exclusive";
-	case QTMOUNT:   return "mounted-channel";
-	case QTAUTH:    return "authentication";
-	case QTTMP:     return "non-backed-up";
-	case QTSYMLINK: return "symlink";
-	case QTFILE:    return "file";
-	}
-
-	return "unknown";
-}
-
-static void
-hexdump_ppline(int x, uint8_t *data, size_t len)
-{
-	for (; x < 50; x++)
-		printf(" ");
-
-	printf("|");
-
-	for (x = 0; x < (int)len; ++x) {
-		if (isgraph(data[x]))
-			printf("%c", data[x]);
-		else
-			printf(".");
-	}
-
-	printf("|\n");
-}
-
-void
-hexdump(const char *label, uint8_t *data, size_t len)
-{
-	size_t	i;
-	int	x, n;
-
-	/*
-	 * Layout:
-	 * === first block === == second block ==  |........|\n
-	 * first and second block are 8 bytes long (for a total of 48
-	 * columns), plus two separator plus two | plus 16 chars, for
-	 * a total of 68 characters.
-	 */
-
-	printf("\nhexdump \"%s\": (%zu bytes)\n", label, len);
-	for (x = 0, n = 0, i = 0; i < len; ++i) {
-		if (i != 0 && i % 8 == 0) {
-			printf(" ");
-			x++;
-		}
-
-		if (n == 16) {
-			hexdump_ppline(x, &data[i - 16], 16);
-			x = 0;
-			n = 0;
-		}
-
-		printf("%02x ", data[i]);
-		x += 3;
-		n++;
-	}
-
-	if (n != 0)
-                hexdump_ppline(x, &data[i - n], n);
-
-	printf("\n");
-}
-
-void
-imsg_event_add(struct imsgev *iev)
-{
-	iev->events = EV_READ;
-	if (iev->ibuf.w.queued)
-		iev->events |= EV_WRITE;
-
-	event_del(&iev->ev);
-	event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev);
-	event_add(&iev->ev, NULL);
-}
-
-int
-imsg_compose_event(struct imsgev *iev, uint16_t type, uint32_t peerid,
-    pid_t pid, int fd, const void *data, uint16_t datalen)
-{
-	int	ret;
-
-	if ((ret = imsg_compose(&iev->ibuf, type, peerid, pid, fd, data,
-	    datalen) != -1))
-		imsg_event_add(iev);
-
-	return ret;
-}
blob - 55b0bbe929a564d5e891da783db89f48371265d8 (mode 644)
blob + /dev/null
--- utils.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef UTILS_H
-#define UTILS_H
-
-#include "compat.h"
-
-struct imsgev;
-
-void	*xmalloc(size_t);
-void	*xcalloc(size_t, size_t);
-char	*xstrdup(const char *);
-void	*xmemdup(const void *, size_t);
-
-const char	*pp_msg_type(uint8_t);
-const char	*pp_qid_type(uint8_t);
-
-void		hexdump(const char *, uint8_t *data, size_t len);
-
-void	imsg_event_add(struct imsgev *);
-int	imsg_compose_event(struct imsgev *, uint16_t, uint32_t, pid_t,
-	    int, const void *, uint16_t);
-
-#endif