/* lib9p/srv.c - 9P server
 *
 * Copyright (C) 2024-2025  Luke T. Shumaker <lukeshu@lukeshu.com>
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

#include <alloca.h>
#include <inttypes.h> /* for PRI* */

#include <libcr/coroutine.h>
#include <libcr_ipc/chan.h>
#include <libcr_ipc/mutex.h>
#include <libcr_ipc/select.h>
#include <libmisc/assert.h>
#include <libhw/generic/net.h>

#define LOG_NAME 9P_SRV
#include <libmisc/log.h>

#define IMPLEMENTATION_FOR_LIB9P_SRV_H YES
#include <lib9p/srv.h>

#include "internal.h"

#ifndef CONFIG_9P_SRV_MAX_FIDS
	#error config.h must define CONFIG_9P_SRV_MAX_FIDS
#endif
#ifndef CONFIG_9P_SRV_MAX_REQS
	#error config.h must define CONFIG_9P_SRV_MAX_REQS
#endif
#ifndef CONFIG_9P_SRV_MAX_DEPTH
	/* 1=just the root dir, 2=just files in the root dir, 3=1 subdir, ... */
	#error config.h must define CONFIG_9P_SRV_MAX_DEPTH
#endif

/* context ********************************************************************/

bool lib9p_srv_flush_requested(struct lib9p_srv_ctx *ctx) {
	assert(ctx);
	return _lib9p_srv_flushch_can_send(&ctx->_flushch);
}

int lib9p_srv_acknowledge_flush(struct lib9p_srv_ctx *ctx) {
	assert(ctx);
	assert(_lib9p_srv_flushch_can_send(&ctx->_flushch));
	lib9p_error(&ctx->basectx, LINUX_ECANCELED, "request canceled by flush");
	_lib9p_srv_flushch_send(&ctx->_flushch, true);
	return -1;
}

/* structs ********************************************************************/

typedef typeof( ((struct lib9p_qid){}).path ) srv_path_t;

struct srv_pathinfo {
	lo_interface lib9p_srv_file      file;
	srv_path_t                       parent_dir;

	/* References from other srv_pathinfos (via .parent_dir) or
	 * from FIDs. */
	unsigned int                     gc_refcount;
	/* References from fids with FIDFLAG_OPEN_R/FIDFLAG_OPEN_W.  */
	unsigned int                     rd_refcount;
	unsigned int                     wr_refcount;
};

#define NAME  pathmap
#define KEY_T srv_path_t
#define VAL_T struct srv_pathinfo
/*              ( naive                                        ) + ( working space for walk() ) */
#define CAP   ( (CONFIG_9P_SRV_MAX_FIDS*CONFIG_9P_SRV_MAX_DEPTH) + (CONFIG_9P_SRV_MAX_REQS*2) )
#include "map.h"

#define FIDFLAG_OPEN_R (1<<0)
#define FIDFLAG_OPEN_W (1<<1)
#define FIDFLAG_RCLOSE (1<<2)
#define FIDFLAG_OPEN   (FIDFLAG_OPEN_R|FIDFLAG_OPEN_W)

struct _srv_fidinfo {
	srv_path_t                       path;
	uint8_t                          flags;
	uint32_t                         iounit;
	size_t                           dir_idx;
	uint32_t                         dir_off;
};

#define NAME  fidmap
#define KEY_T lib9p_fid_t
#define VAL_T struct _srv_fidinfo
#define CAP   CONFIG_9P_SRV_MAX_FIDS
#include "map.h"

#define NAME  reqmap
#define KEY_T lib9p_tag_t
#define VAL_T struct _lib9p_srv_req *
#define CAP   CONFIG_9P_SRV_MAX_REQS
#include "map.h"

/* The hierarchy of concepts is:
 *
 *     server -> connection -> session -> request
 *
 */

/* struct _srv_srv {} is defined in <lib9p/srv.h> */

struct _srv_conn {
	/* immutable */
	struct lib9p_srv                *parent_srv;
	lo_interface net_stream_conn     fd;
	cid_t                            reader; /* the lib9p_srv_read_cr() coroutine */
	/* mutable */
	cr_mutex_t                       writelock;
};

struct _srv_sess {
	/* immutable */
	struct _srv_conn        *parent_conn;
	enum lib9p_version       version;
	uint32_t                 max_msg_size;
	uint32_t                 rerror_overhead;
	/* mutable */
	bool                     initialized;
	bool                     closing;
	struct pathmap           paths;
	struct reqmap            reqs;
	struct fidmap            fids;
};

struct _lib9p_srv_req {
	/* immutable */
	struct _srv_sess        *parent_sess;
	uint16_t                 tag;
	uint8_t                 *net_bytes;
	/* mutable */
	struct lib9p_srv_ctx     ctx;
};

/* base utilities *************************************************************/

#define nonrespond_errorf errorf

static ssize_t write_Rmsg(struct _lib9p_srv_req *req, struct lib9p_Rmsg_send_buf *resp) {
	ssize_t r = 0, _r;
	cr_mutex_lock(&req->parent_sess->parent_conn->writelock);
	for (size_t i = 0; i < resp->iov_cnt; i++) {
		_r = LO_CALL(req->parent_sess->parent_conn->fd, write,
		             resp->iov[i].iov_base, resp->iov[i].iov_len);
		if (_r < 0)
			return _r;
		r += _r;
	}
	cr_mutex_unlock(&req->parent_sess->parent_conn->writelock);
	return r;
}

static void respond_error(struct _lib9p_srv_req *req) {
#if CONFIG_9P_ENABLE_9P2000_u
	assert(req->ctx.basectx.err_num);
#endif
	assert(req->ctx.basectx.err_msg[0]);

	ssize_t r;
	struct lib9p_msg_Rerror host = {
		.tag = req->tag,
		.ename = lib9p_strn(req->ctx.basectx.err_msg,
		                    CONFIG_9P_MAX_ERR_SIZE),
#if CONFIG_9P_ENABLE_9P2000_u
		.errno = req->ctx.basectx.err_num,
#endif
	};

	struct _srv_sess *sess = req->parent_sess;

	/* Truncate the error-string if necessary to avoid needing to
	 * return LINUX_ERANGE.  */
	if (((uint32_t)host.ename.len) + sess->rerror_overhead > sess->max_msg_size)
		host.ename.len = sess->max_msg_size - sess->rerror_overhead;

	struct lib9p_Rmsg_send_buf net;

	lib9p_Rmsg_marshal(&req->ctx.basectx,
	                   LIB9P_TYP_Rerror, &host,
	                   &net);

	r = write_Rmsg(req, &net);
	if (r < 0)
		nonrespond_errorf("write: %s", net_strerror(-r));
}

/* read coroutine *************************************************************/

static bool read_exactly(lo_interface net_stream_conn fd, uint8_t *buf, size_t goal, size_t *done) {
	assert(buf);
	assert(goal);
	assert(done);
	while (*done < goal) {
		ssize_t r = LO_CALL(fd, read, &buf[*done], goal - *done);
		if (r < 0) {
			nonrespond_errorf("read: %s", net_strerror(-r));
			return true;
		} else if (r == 0) {
			if (*done != 0)
				nonrespond_errorf("read: unexpected EOF");
			return true;
		}
		*done += r;
	}
	return false;
}

static void handle_message(struct _lib9p_srv_req *ctx);

[[noreturn]] void lib9p_srv_read_cr(struct lib9p_srv *srv, lo_interface net_stream_listener listener) {
	assert(srv);
	assert(srv->rootdir);
	assert(!LO_IS_NULL(listener));

	srv->readers++;

	uint32_t initial_rerror_overhead = _lib9p_table_msg_min_size[LIB9P_VER_unknown];

	for (;;) {
		struct _srv_conn conn = {
			.parent_srv = srv,
			.fd         = LO_CALL(listener, accept),
			.reader     = cr_getcid(),
		};
		if (LO_IS_NULL(conn.fd)) {
			nonrespond_errorf("accept: error");
			srv->readers--;
			if (srv->readers == 0)
				while (srv->writers > 0)
					_lib9p_srv_reqch_send_req(&srv->_reqch, NULL);
			cr_exit();
		}

		struct _srv_sess sess = {
			.parent_conn     = &conn,
			.version         = LIB9P_VER_unknown,
			.max_msg_size    = CONFIG_9P_MAX_MSG_SIZE,
			.rerror_overhead = initial_rerror_overhead,
			.initialized     = false,
		};
		for (;;) {
		nextmsg:
			/* Read the message.  */
			size_t done = 0;
			uint8_t buf[7];
			if (read_exactly(conn.fd, buf, 4, &done))
				goto close;
			size_t goal = uint32le_decode(buf);
			if (goal < 7) {
				nonrespond_errorf("T-message is impossibly small");
				goto close;
			}
			if (read_exactly(conn.fd, buf, 7, &done))
				goto close;
			struct _lib9p_srv_req req = {
				.parent_sess = &sess,
				.tag         = uint16le_decode(&buf[5]),
				.net_bytes   = buf,
				.ctx         = {
					.basectx = {
						.version      = sess.version,
						.max_msg_size = sess.max_msg_size,
					},
				},
			};
			if (goal > sess.max_msg_size) {
				lib9p_errorf(&req.ctx.basectx,
				             LINUX_EMSGSIZE, "T-message larger than %s limit (%zu > %"PRIu32")",
				             sess.initialized ? "negotiated" : "server",
				             goal,
				             sess.max_msg_size);
				respond_error(&req);
				goto nextmsg;
			}
			req.net_bytes = malloc(goal);
			assert(req.net_bytes);
			memcpy(req.net_bytes, buf, done);
			if (read_exactly(conn.fd, req.net_bytes, goal, &done)) {
				free(req.net_bytes);
				goto close;
			}

			/* Handle the message...  */
			if (req.net_bytes[4] == LIB9P_TYP_Tversion)
				/* ...in this coroutine for Tversion,  */
				handle_message(&req);
			else
				/* ...but usually in another coroutine.  */
				_lib9p_srv_reqch_send_req(&srv->_reqch, &req);
		}
	close:
		LO_CALL(conn.fd, close, true, sess.reqs.len == 0);
		if (sess.reqs.len) {
			sess.closing = true;
			cr_pause_and_yield();
			assert(sess.reqs.len == 0);
			LO_CALL(conn.fd, close, true, true);
		}
	}
}

/* write coroutine ************************************************************/

COROUTINE lib9p_srv_write_cr(void *_srv) {
	struct _lib9p_srv_req   req;
	_lib9p_srv_reqch_req_t  rpc_handle;

	struct lib9p_srv *srv = _srv;
	assert(srv);
	assert(srv->rootdir);
	cr_begin();

	srv->writers++;

	for (;;) {
		/* Receive the request from the reader coroutine.  ************/
		rpc_handle = _lib9p_srv_reqch_recv_req(&srv->_reqch);
		if (!rpc_handle.req) {
			srv->writers--;
			_lib9p_srv_reqch_send_resp(rpc_handle, 0);
			cr_exit();
		}
		/* Copy the request from the reader coroutine's
		 * stack to our stack.  */
		req = *rpc_handle.req;
		/* Record that we have it.  */
		reqmap_store(&req.parent_sess->reqs, req.tag, &req);
		/* Notify the reader coroutine that we're done with
		 * its data.  */
		_lib9p_srv_reqch_send_resp(rpc_handle, 0);

		/* Process the request.  **************************************/
		handle_message(&req);

		/* Release resources.  ****************************************/
		while (_lib9p_srv_flushch_can_send(&req.ctx._flushch))
			_lib9p_srv_flushch_send(&req.ctx._flushch, false);
		reqmap_del(&req.parent_sess->reqs, req.tag);
		if (req.parent_sess->closing && !req.parent_sess->reqs.len)
			cr_unpause(req.parent_sess->parent_conn->reader);
	}

	cr_end();
}

#define _HANDLER_PROTO(typ)                                  \
	static void handle_T##typ(struct _lib9p_srv_req *,   \
	                          struct lib9p_msg_T##typ *, \
	                          struct lib9p_msg_R##typ *)
_HANDLER_PROTO(version);
_HANDLER_PROTO(auth);
_HANDLER_PROTO(attach);
_HANDLER_PROTO(flush);
_HANDLER_PROTO(walk);
_HANDLER_PROTO(open);
_HANDLER_PROTO(create);
_HANDLER_PROTO(read);
_HANDLER_PROTO(write);
_HANDLER_PROTO(clunk);
_HANDLER_PROTO(remove);
_HANDLER_PROTO(stat);
_HANDLER_PROTO(wstat);
#if CONFIG_9P_ENABLE_9P2000_e
_HANDLER_PROTO(session);
_HANDLER_PROTO(sread);
_HANDLER_PROTO(swrite);
#endif

typedef void (*tmessage_handler)(struct _lib9p_srv_req *, void *, void *);

static tmessage_handler tmessage_handlers[0x100] = {
	[LIB9P_TYP_Tversion] = (tmessage_handler)handle_Tversion,
	[LIB9P_TYP_Tauth]    = (tmessage_handler)handle_Tauth,
	[LIB9P_TYP_Tattach]  = (tmessage_handler)handle_Tattach,
	[LIB9P_TYP_Tflush]   = (tmessage_handler)handle_Tflush,
	[LIB9P_TYP_Twalk]    = (tmessage_handler)handle_Twalk,
	[LIB9P_TYP_Topen]    = (tmessage_handler)handle_Topen,
	[LIB9P_TYP_Tcreate]  = (tmessage_handler)handle_Tcreate,
	[LIB9P_TYP_Tread]    = (tmessage_handler)handle_Tread,
	[LIB9P_TYP_Twrite]   = (tmessage_handler)handle_Twrite,
	[LIB9P_TYP_Tclunk]   = (tmessage_handler)handle_Tclunk,
	[LIB9P_TYP_Tremove]  = (tmessage_handler)handle_Tremove,
	[LIB9P_TYP_Tstat]    = (tmessage_handler)handle_Tstat,
	[LIB9P_TYP_Twstat]   = (tmessage_handler)handle_Twstat,
#if CONFIG_9P_ENABLE_9P2000_e
	[LIB9P_TYP_Tsession] = (tmessage_handler)handle_Tsession,
	[LIB9P_TYP_Tsread]   = (tmessage_handler)handle_Tsread,
	[LIB9P_TYP_Tswrite]  = (tmessage_handler)handle_Tswrite,
#endif
};

static void handle_message(struct _lib9p_srv_req *ctx) {
	uint8_t *host_req = NULL;
	uint8_t host_resp[CONFIG_9P_MAX_HOSTMSG_SIZE];

	/* Unmarshal it.  */
	ssize_t host_size = lib9p_Tmsg_validate(&ctx->ctx.basectx, ctx->net_bytes);
	if (host_size < 0)
		goto write;
	host_req = malloc(host_size);
	assert(host_req);
	enum lib9p_msg_type typ;
	lib9p_Tmsg_unmarshal(&ctx->ctx.basectx, ctx->net_bytes,
	                &typ, host_req);

	/* Handle it.  */
	tmessage_handlers[typ](ctx, (void *)host_req, (void *)host_resp);

 write:
	if (lib9p_ctx_has_error(&ctx->ctx.basectx))
		respond_error(ctx);
	else {
		struct lib9p_Rmsg_send_buf net_resp;
		if (lib9p_Rmsg_marshal(&ctx->ctx.basectx,
		                       typ+1, host_resp,
		                       &net_resp))
			goto write;
		write_Rmsg(ctx, &net_resp);
	}
	if (host_req)
		free(host_req);
	free(ctx->net_bytes);
}

#define util_handler_common(ctx, req, resp) do { \
		assert(ctx);                     \
		assert(req);                     \
		assert(resp);                    \
		resp->tag = req->tag;            \
	} while (0)

static inline bool srv_util_check_perm(struct _lib9p_srv_req *ctx, struct lib9p_stat *stat, uint8_t action) {
	assert(ctx);
	assert(stat);
	assert(action);

	/* TODO actually check user and group instead of just assuming "other".  */
	uint8_t mode = (uint8_t)(stat->file_mode & 07);

	return mode & action;
}

/**
 * Ensures that `file` is saved into the pathmap, and increments the
 * gc_refcount by 1 (for presumptive insertion into the fidmap).
 * parent_path's gc_refcount is also incremented as appropriate.
 *
 * Returns a pointer to the stored pathinfo.
 */
static inline struct srv_pathinfo *srv_util_pathsave(struct _lib9p_srv_req *ctx,
                                                     lo_interface lib9p_srv_file file,
                                                     srv_path_t parent_path) {
	assert(ctx);
	assert(!LO_IS_NULL(file));

	struct lib9p_qid qid = LO_CALL(file, qid);
	struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, qid.path);
	if (pathinfo)
		assert(LO_EQ(pathinfo->file, file));
	else {
		pathinfo = pathmap_store(&ctx->parent_sess->paths, qid.path,
			(struct srv_pathinfo){
				.file        = file,
				.parent_dir  = parent_path,
				.gc_refcount = 0,
				.rd_refcount = 0,
				.wr_refcount = 0,
			});
		assert(pathinfo);
		if (parent_path != qid.path) {
			struct srv_pathinfo *parent = pathmap_load(&ctx->parent_sess->paths, parent_path);
			assert(parent);
			parent->gc_refcount++;
		}
	}
	pathinfo->gc_refcount++;
	return pathinfo;
}

/**
 * Decrement the path's gc_refcount, and trigger garbage collection as
 * appropriate.
 */
static inline void srv_util_pathfree(struct _lib9p_srv_req *ctx, srv_path_t path) {
	assert(ctx);

	struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, path);
	assert(pathinfo);
	pathinfo->gc_refcount--;
	if (pathinfo->gc_refcount == 0) {
		if (pathinfo->parent_dir != path)
			srv_util_pathfree(ctx, pathinfo->parent_dir);
		LO_CALL(pathinfo->file, free);
		pathmap_del(&ctx->parent_sess->paths, path);
	}
}

static inline bool srv_util_pathisdir(struct srv_pathinfo *pathinfo) {
	assert(pathinfo);
	return LO_CALL(pathinfo->file, qid).type & LIB9P_QT_DIR;
}

/**
 * Store fid as pointing to pathinfo.  Assumes that
 * pathinfo->gc_refcount has already been incremented; does *not*
 * decrement it on failure.
 */
static inline struct _srv_fidinfo *srv_util_fidsave(struct _lib9p_srv_req *ctx, lib9p_fid_t fid, struct srv_pathinfo *pathinfo, bool overwrite) {
	assert(ctx);
	assert(fid != LIB9P_FID_NOFID);
	assert(pathinfo);

	struct lib9p_qid qid = LO_CALL(pathinfo->file, qid);

	struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, fid);
	if (fidinfo) {
		if (overwrite) {
			srv_util_pathfree(ctx, fidinfo->path);
		} else {
			lib9p_error(&ctx->ctx.basectx,
			            LINUX_EBADF, "FID already in use");
			return NULL;
		}
	} else {
		fidinfo = fidmap_store(&ctx->parent_sess->fids, fid,  (struct _srv_fidinfo){});
		if (!fidinfo) {
			lib9p_error(&ctx->ctx.basectx,
			            LINUX_EMFILE, "too many open files");
			return NULL;
		}
	}
	*fidinfo = (struct _srv_fidinfo){
		.path  = qid.path,
	};
	return fidinfo;
}


static void handle_Tversion(struct _lib9p_srv_req *ctx,
                            struct lib9p_msg_Tversion *req,
                            struct lib9p_msg_Rversion *resp) {
	util_handler_common(ctx, req, resp);

	enum lib9p_version version = LIB9P_VER_unknown;

	if (req->version.len >= 6 &&
	    req->version.utf8[0] == '9' &&
	    req->version.utf8[1] == 'P' &&
	    '0' <= req->version.utf8[2] && req->version.utf8[2] <= '9' &&
	    '0' <= req->version.utf8[3] && req->version.utf8[3] <= '9' &&
	    '0' <= req->version.utf8[4] && req->version.utf8[4] <= '9' &&
	    '0' <= req->version.utf8[5] && req->version.utf8[5] <= '9' &&
	    (req->version.len == 6 || req->version.utf8[6] == '.')) {
		version = LIB9P_VER_9P2000;
#if CONFIG_9P_ENABLE_9P2000_u
		if (lib9p_str_eq(lib9p_str_sliceleft(req->version, 6), lib9p_str(".u")))
			version = LIB9P_VER_9P2000_u;
#endif
#if CONFIG_9P_ENABLE_9P2000_e
		if (lib9p_str_eq(lib9p_str_sliceleft(req->version, 6), lib9p_str(".e")))
			version = LIB9P_VER_9P2000_e;
#endif
	}

	uint32_t min_msg_size = _lib9p_table_msg_min_size[version];
	if (req->max_msg_size < min_msg_size) {
		lib9p_errorf(&ctx->ctx.basectx,
		             LINUX_EDOM, "requested max_msg_size is less than minimum for %s (%"PRIu32" < %"PRIu32")",
		             version, req->max_msg_size, min_msg_size);
		return;
	}

	resp->version = lib9p_str((char *)lib9p_version_str(version)); /* cast to discard "const" qualifier */
	resp->max_msg_size = (CONFIG_9P_MAX_MSG_SIZE < req->max_msg_size)
		? CONFIG_9P_MAX_MSG_SIZE
		: req->max_msg_size;

	/* Close the old session.  */
	if (ctx->parent_sess->reqs.len) {
		/* Flush all in-progress requests, and wait for them
		 * to finish.  */
		struct cr_select_arg *list = alloca(sizeof(struct cr_select_arg) * ctx->parent_sess->reqs.len);
		while (ctx->parent_sess->reqs.len) {
			uint16_t tag [[gnu::unused]];
			struct _lib9p_srv_req **reqpp;
			size_t i = 0;
			bool flushed;
			MAP_FOREACH(&ctx->parent_sess->reqs, tag, reqpp) {
				list[i] = CR_SELECT_RECV(&((*reqpp)->ctx._flushch), &flushed);
			}
			cr_select_v(i, list);
		}
	}
	if (ctx->parent_sess->fids.len) {
		/* Close all FIDs.  */
		uint32_t fid;
		struct _srv_fidinfo *fidinfo [[gnu::unused]];
		MAP_FOREACH(&ctx->parent_sess->fids, fid, fidinfo) {
			handle_Tclunk(ctx,
			              &(struct lib9p_msg_Tclunk){.fid = fid},
			              &(struct lib9p_msg_Rclunk){});
		}
	}

	/* Replace the old session with the new session.  */
	ctx->parent_sess->version = version;
	ctx->parent_sess->max_msg_size = resp->max_msg_size;
	ctx->parent_sess->rerror_overhead = min_msg_size;
}

static void handle_Tauth(struct _lib9p_srv_req *ctx,
                         struct lib9p_msg_Tauth *req,
                         struct lib9p_msg_Rauth *resp) {
	util_handler_common(ctx, req, resp);

	ctx->ctx.uid = req->n_uid;
	ctx->ctx.uname = req->uname;
	struct lib9p_srv *srv = ctx->parent_sess->parent_conn->parent_srv;

	if (!srv->auth) {
		lib9p_error(&ctx->ctx.basectx,
		            LINUX_EOPNOTSUPP, "authentication not required");
		return;
	}

	srv->auth(&ctx->ctx, req->aname);
	lib9p_error(&ctx->ctx.basectx,
	            LINUX_EOPNOTSUPP, "TODO: auth not implemented");
}

static void handle_Tattach(struct _lib9p_srv_req *ctx,
                           struct lib9p_msg_Tattach *req,
                           struct lib9p_msg_Rattach *resp) {
	util_handler_common(ctx, req, resp);

	ctx->ctx.uid = req->n_uid;
	ctx->ctx.uname = req->uname;
	struct lib9p_srv *srv = ctx->parent_sess->parent_conn->parent_srv;

	if (srv->auth) {
		/*
		struct lib9p_srv_filehandle *fh = fidmap_get(req->afid);
		if (!fh)
			lib9p_error(&ctx->ctx.basectx,
			            LINUX_EACCES, "FID provided as auth-file is not a valid FID");
		else if (fh->type != FH_AUTH)
			lib9p_error(&ctx->ctx.basectx,
			            LINUX_EACCES, "FID provided as auth-file is not an auth-file");
		else if (!lib9p_str_eq(fh->data.auth.uname, req->uname))
			lib9p_errorf(&ctx->ctx.basectx,
			             LINUX_EACCES, "FID provided as auth-file is for user=\"%.*s\" and cannot be used for user=\"%.*s\"",
			             fh->data.auth.uname.len, fh->data.auth.uname.utf8,
			             req->uname.len, req->uname.utf8);
		else if (!lib9p_str_eq(fh->data.auth.aname, req->aname))
			lib9p_errorf(&ctx->ctx.basectx,
			             LINUX_EACCES, "FID provided as auth-file is for tree=\"%.*s\" and cannot be used for tree=\"%.*s\"",
			             fh->data.auth.aname.len, fh->data.auth.aname.utf8,
			             req->aname.len, req->aname.utf8);
		else if (!fh->data.auth.authenticated)
			lib9p_error(&ctx->ctx.basectx,
			            LINUX_EACCES, "FID provided as auth-file has not completed authentication");
		fh->refcount--;
		if (lib9p_ctx_has_error(&ctx->ctx))
			return;
		*/
		lib9p_error(&ctx->ctx.basectx,
		            LINUX_EOPNOTSUPP, "TODO: auth not (yet?) implemented");
		return;
	} else {
		if (req->afid != LIB9P_FID_NOFID) {
			lib9p_error(&ctx->ctx.basectx,
			            LINUX_EACCES, "FID provided as auth-file, but no auth-file is required");
			return;
		}
	}

	if (req->fid == LIB9P_FID_NOFID) {
			lib9p_error(&ctx->ctx.basectx,
			            LINUX_EBADF, "cannot assign to NOFID");
			return;
	}

	/* 1. File object */
	lo_interface lib9p_srv_file root_file = srv->rootdir(&ctx->ctx, req->aname);
	assert(LO_IS_NULL(root_file) == lib9p_ctx_has_error(&ctx->ctx.basectx));
	if (lib9p_ctx_has_error(&ctx->ctx.basectx))
		return;

	struct lib9p_qid root_qid = LO_CALL(root_file, qid);
	assert(root_qid.type & LIB9P_QT_DIR);

	/* 2. pathinfo */
	struct srv_pathinfo *root_pathinfo = srv_util_pathsave(ctx, root_file, root_qid.path);

	/* 3. fidinfo */
	if (!srv_util_fidsave(ctx, req->fid, root_pathinfo, false)) {
		srv_util_pathfree(ctx, root_qid.path);
		return;
	}

	resp->qid = root_qid;
	return;
}

static void handle_Tflush(struct _lib9p_srv_req *ctx,
                          struct lib9p_msg_Tflush *req,
                          struct lib9p_msg_Rflush *resp) {
	util_handler_common(ctx, req, resp);

	struct _lib9p_srv_req **oldreqp = reqmap_load(&ctx->parent_sess->reqs, req->oldtag);
	if (oldreqp)
		_lib9p_srv_flushch_recv(&((*oldreqp)->ctx._flushch));
}

static void handle_Twalk(struct _lib9p_srv_req *ctx,
                         struct lib9p_msg_Twalk *req,
                         struct lib9p_msg_Rwalk *resp) {
	util_handler_common(ctx, req, resp);

	if (req->newfid == LIB9P_FID_NOFID) {
			lib9p_error(&ctx->ctx.basectx,
			            LINUX_EBADF, "cannot assign to NOFID");
			return;
	}

	struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, req->fid);
	if (!fidinfo) {
		lib9p_errorf(&ctx->ctx.basectx,
		             LINUX_EBADF, "bad file number %"PRIu32, req->fid);
		return;
	}

	struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path);
	assert(pathinfo);
	pathinfo->gc_refcount++;

	resp->wqid = (struct lib9p_qid *)(&resp[1]);
	for (resp->nwqid = 0; resp->nwqid < req->nwname; resp->nwqid++) {
		struct srv_pathinfo *new_pathinfo;
		if (lib9p_str_eq(req->wname[resp->nwqid], lib9p_str(".."))) {
			new_pathinfo = pathmap_load(&ctx->parent_sess->paths, pathinfo->parent_dir);
			assert(new_pathinfo);
			new_pathinfo->gc_refcount++;
		} else {
			if (!srv_util_pathisdir(pathinfo)) {
				lib9p_error(&ctx->ctx.basectx,
				            LINUX_ENOTDIR, "not a directory");
				break;
			}

			lo_interface lib9p_srv_file member_file = LO_CALL(pathinfo->file, dopen, &ctx->ctx, req->wname[resp->nwqid]);
			assert(LO_IS_NULL(member_file) == lib9p_ctx_has_error(&ctx->ctx.basectx));
			if (lib9p_ctx_has_error(&ctx->ctx.basectx))
				break;
			new_pathinfo = srv_util_pathsave(ctx, member_file, LO_CALL(pathinfo->file, qid).path);
		}

		if (srv_util_pathisdir(new_pathinfo)) {
			struct lib9p_stat stat = LO_CALL(new_pathinfo->file, stat, &ctx->ctx);
			if (lib9p_ctx_has_error(&ctx->ctx.basectx))
				break;
			lib9p_stat_assert(stat);
			if (!srv_util_check_perm(ctx, &stat, 0b001)) {
				lib9p_error(&ctx->ctx.basectx,
				            LINUX_EACCES, "you do not have execute permission on that directory");
				srv_util_pathfree(ctx, LO_CALL(new_pathinfo->file, qid).path);
				break;
			}
		}

		resp->wqid[resp->nwqid] = LO_CALL(new_pathinfo->file, qid);

		srv_util_pathfree(ctx, LO_CALL(pathinfo->file, qid).path);
		pathinfo = new_pathinfo;
	}
	if (resp->nwqid == req->nwname) {
		if (!srv_util_fidsave(ctx, req->newfid, pathinfo, req->newfid == req->fid))
			srv_util_pathfree(ctx, LO_CALL(pathinfo->file, qid).path);
	} else {
		assert(lib9p_ctx_has_error(&ctx->ctx.basectx));
		srv_util_pathfree(ctx, LO_CALL(pathinfo->file, qid).path);
		if (resp->nwqid > 0)
			lib9p_ctx_clear_error(&ctx->ctx.basectx);
	}
}

static void handle_Topen(struct _lib9p_srv_req *ctx,
                         struct lib9p_msg_Topen *req,
                         struct lib9p_msg_Ropen *resp) {
	util_handler_common(ctx, req, resp);

	/* Check that the FID is valid for this.  */
	struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, req->fid);
	if (!fidinfo) {
		lib9p_errorf(&ctx->ctx.basectx,
		             LINUX_EBADF, "bad file number %"PRIu32, req->fid);
		return;
	}
	if (fidinfo->flags & FIDFLAG_OPEN) {
		lib9p_error(&ctx->ctx.basectx,
		            LINUX_EALREADY, "FID is already open");
		return;
	}
	struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path);
	assert(pathinfo);
	if (srv_util_pathisdir(pathinfo)) {
		if ( ((req->mode & LIB9P_O_MODE_MASK) != LIB9P_O_READ) ||
		     (req->mode & LIB9P_O_TRUNC) ||
		     (req->mode & LIB9P_O_RCLOSE) ) {
			lib9p_error(&ctx->ctx.basectx,
			            LINUX_EISDIR, "directories cannot be written, executed, truncated, or removed-on-close");
			return;
		}
	}

	/* Variables.  */
	lib9p_o_t       reqmode  = req->mode;
	uint8_t         fidflags = fidinfo->flags;
	uint32_t        iounit   = fidinfo->iounit;

	/* Check permissions.  */
	if (reqmode & LIB9P_O_RCLOSE) {
		struct srv_pathinfo *parent = pathmap_load(&ctx->parent_sess->paths, pathinfo->parent_dir);
		assert(parent);
		struct lib9p_stat parent_stat = LO_CALL(parent->file, stat, &ctx->ctx);
		if (lib9p_ctx_has_error(&ctx->ctx.basectx))
			return;
		lib9p_stat_assert(parent_stat);
		if (!srv_util_check_perm(ctx, &parent_stat, 0b010)) {
			lib9p_error(&ctx->ctx.basectx,
			            LINUX_EACCES, "permission denied to remove-on-close");
			return;
		}
		fidflags = fidflags | FIDFLAG_RCLOSE;
	}
	struct lib9p_stat stat = LO_CALL(pathinfo->file, stat, &ctx->ctx);
	if (lib9p_ctx_has_error(&ctx->ctx.basectx))
		return;
	lib9p_stat_assert(stat);
	if ((stat.file_mode & LIB9P_DM_EXCL) && (pathinfo->rd_refcount || pathinfo->wr_refcount)) {
		lib9p_error(&ctx->ctx.basectx,
		            LINUX_EEXIST, "exclusive file is already opened");
		return;
	}
	if (stat.file_mode & LIB9P_DM_APPEND)
		reqmode = reqmode & ~LIB9P_O_TRUNC;
	uint8_t perm_bits = 0;
	bool rd = false, wr = false;
	switch (reqmode & LIB9P_O_MODE_MASK) {
	case LIB9P_O_READ:
		perm_bits = 0b100;
		rd = true;
		break;
	case LIB9P_O_WRITE:
		perm_bits = 0b010;
		wr = true;
		break;
	case LIB9P_O_RDWR:
		perm_bits = 0b110;
		rd = wr = true;
		break;
	case LIB9P_O_EXEC:
		perm_bits = 0b001;
		rd = true;
		break;
	}
	if (!srv_util_check_perm(ctx, &stat, perm_bits)) {
		lib9p_error(&ctx->ctx.basectx,
		            LINUX_EACCES, "permission denied");
	}

	/* Actually make the call.  */
	if ((reqmode & LIB9P_O_TRUNC) || (rd && !pathinfo->rd_refcount) || (wr && !pathinfo->wr_refcount) ) {
		iounit = LO_CALL(pathinfo->file, chio, &ctx->ctx,
		                 fidflags & FIDFLAG_OPEN_R,
		                 fidflags & FIDFLAG_OPEN_W,
		                 reqmode & LIB9P_O_TRUNC);
		if (lib9p_ctx_has_error(&ctx->ctx.basectx))
			return;
	}

	/* Success.  */
	if (rd) {
		fidflags = fidflags | FIDFLAG_OPEN_R;
		pathinfo->rd_refcount++;
	}
	if (wr) {
		fidflags = fidflags | FIDFLAG_OPEN_W;
		pathinfo->wr_refcount++;
	}
	fidinfo->flags = fidflags;
	resp->qid = stat.file_qid;
	resp->iounit = iounit;
}

static void handle_Tcreate(struct _lib9p_srv_req *ctx,
                           struct lib9p_msg_Tcreate *req,
                           struct lib9p_msg_Rcreate *resp) {
	util_handler_common(ctx, req, resp);

	lib9p_error(&ctx->ctx.basectx,
	            LINUX_EOPNOTSUPP, "create not (yet?) implemented");
}

static void handle_Tread(struct _lib9p_srv_req *ctx,
                         struct lib9p_msg_Tread *req,
                         struct lib9p_msg_Rread *resp) {
	util_handler_common(ctx, req, resp);

	/* Check that the FID is valid for this.  */
	struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, req->fid);
	if (!fidinfo) {
		lib9p_errorf(&ctx->ctx.basectx,
		             LINUX_EBADF, "bad file number %"PRIu32, req->fid);
		return;
	}
	if (!(fidinfo->flags & FIDFLAG_OPEN_R)) {
		lib9p_error(&ctx->ctx.basectx,
		            LINUX_EINVAL, "FID not open for reading");
		return;
	}

	/* Variables.  */
	struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path);
	assert(pathinfo);
	resp->data = (char *)(&resp[1]);

	/* Do it.  */
	if (srv_util_pathisdir(pathinfo)) {
		/* Translate byte-offset to object-index. */
		size_t idx;
		if (req->offset == 0)
			idx = 0;
		else if (req->offset == fidinfo->dir_off)
			idx = fidinfo->dir_idx;
		else {
			lib9p_errorf(&ctx->ctx.basectx,
			             LINUX_EINVAL, "invalid offset (must be 0 or %"PRIu32"): %"PRIu32,
			             fidinfo->dir_off, req->offset);
			return;
		}
		/* Do it.  */
		size_t num = LO_CALL(pathinfo->file, dread, &ctx->ctx, (uint8_t *)resp->data, req->count, idx);
		/* Translate object-count back to byte-count.  */
		uint32_t len = 0;
		for (size_t i = 0; i < num; i++) {
			uint32_t i_len;
			lib9p_stat_validate(&ctx->ctx.basectx, req->count, &((uint8_t *)resp->data)[len], &i_len, NULL);
			len += i_len;
		}
		resp->count = len;
		/* Remember.  */
		fidinfo->dir_idx = idx+num;
		fidinfo->dir_off = req->offset + len;
	} else
		resp->count = LO_CALL(pathinfo->file, pread, &ctx->ctx, resp->data, req->count, req->offset);
}

static void handle_Twrite(struct _lib9p_srv_req *ctx,
                          struct lib9p_msg_Twrite *req,
                          struct lib9p_msg_Rwrite *resp) {
	util_handler_common(ctx, req, resp);

	/* Check that the FID is valid for this.  */
	struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, req->fid);
	if (!fidinfo) {
		lib9p_errorf(&ctx->ctx.basectx,
		             LINUX_EBADF, "bad file number %"PRIu32, req->fid);
		return;
	}
	if (!(fidinfo->flags & FIDFLAG_OPEN_W)) {
		lib9p_error(&ctx->ctx.basectx,
		            LINUX_EINVAL, "FID not open for writing");
		return;
	}

	/* Variables.  */
	struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path);
	assert(pathinfo);

	/* Do it.  */
	resp->count = LO_CALL(pathinfo->file, pwrite, &ctx->ctx, req->data, req->count, req->offset);
}

static void clunkremove(struct _lib9p_srv_req *ctx, lib9p_fid_t fid, bool remove) {
	struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, fid);
	if (!fidinfo) {
		lib9p_errorf(&ctx->ctx.basectx,
		             LINUX_EBADF, "bad file number %"PRIu32, fid);
		return;
	}
	if (fidinfo->flags & FIDFLAG_RCLOSE)
		remove = true;
	struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path);
	assert(pathinfo);

	if (remove) {
		if (pathinfo->parent_dir == fidinfo->path) {
			lib9p_errorf(&ctx->ctx.basectx,
			             LINUX_EBUSY, "cannot remove root");
			goto clunk;
		}
		struct srv_pathinfo *parent = pathmap_load(&ctx->parent_sess->paths, pathinfo->parent_dir);
		assert(parent);
		struct lib9p_stat parent_stat = LO_CALL(parent->file, stat, &ctx->ctx);
		if (!srv_util_check_perm(ctx, &parent_stat, 0b010)) {
			lib9p_error(&ctx->ctx.basectx,
			            LINUX_EACCES, "you do not have write permission on the parent directory");
			goto clunk;
		}
		LO_CALL(pathinfo->file, remove, &ctx->ctx);
	}

 clunk:
	srv_util_pathfree(ctx, LO_CALL(pathinfo->file, qid).path);
	fidmap_del(&ctx->parent_sess->fids, fid);
}

static void handle_Tclunk(struct _lib9p_srv_req *ctx,
                          struct lib9p_msg_Tclunk *req,
                          struct lib9p_msg_Rclunk *resp) {
	util_handler_common(ctx, req, resp);

	clunkremove(ctx, req->fid, false);
}


static void handle_Tremove(struct _lib9p_srv_req *ctx,
                           struct lib9p_msg_Tremove *req,
                           struct lib9p_msg_Rremove *resp) {
	util_handler_common(ctx, req, resp);

	clunkremove(ctx, req->fid, true);
}

static void handle_Tstat(struct _lib9p_srv_req *ctx,
                         struct lib9p_msg_Tstat *req,
                         struct lib9p_msg_Rstat *resp) {
	util_handler_common(ctx, req, resp);

	struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, req->fid);
	if (!fidinfo) {
		lib9p_errorf(&ctx->ctx.basectx,
		             LINUX_EBADF, "bad file number %"PRIu32, req->fid);
		return;
	}
	struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path);
	assert(pathinfo);

	resp->stat = LO_CALL(pathinfo->file, stat, &ctx->ctx);
	if (!lib9p_ctx_has_error(&ctx->ctx.basectx))
		lib9p_stat_assert(resp->stat);
}

static void handle_Twstat(struct _lib9p_srv_req *ctx,
                          struct lib9p_msg_Twstat *req,
                          struct lib9p_msg_Rwstat *resp) {
	util_handler_common(ctx, req, resp);

	lib9p_error(&ctx->ctx.basectx,
	            LINUX_EOPNOTSUPP, "wstat not (yet?) implemented");
}

#if CONFIG_9P_ENABLE_9P2000_e
static void handle_Tsession(struct _lib9p_srv_req *ctx,
                            struct lib9p_msg_Tsession *req,
                            struct lib9p_msg_Rsession *resp) {
	util_handler_common(ctx, req, resp);

	lib9p_error(&ctx->ctx.basectx,
	            LINUX_EOPNOTSUPP, "session not (yet?) implemented");
}

static void handle_Tsread(struct _lib9p_srv_req *ctx,
                          struct lib9p_msg_Tsread *req,
                          struct lib9p_msg_Rsread *resp) {
	util_handler_common(ctx, req, resp);

	lib9p_error(&ctx->ctx.basectx,
	            LINUX_EOPNOTSUPP, "sread not (yet?) implemented");
}

static void handle_Tswrite(struct _lib9p_srv_req *ctx,
                           struct lib9p_msg_Tswrite *req,
                           struct lib9p_msg_Rswrite *resp) {
	util_handler_common(ctx, req, resp);

	lib9p_error(&ctx->ctx.basectx,
	            LINUX_EOPNOTSUPP, "swrite not (yet?) implemented");
}
#endif