#include <assert.h>
#include <alloca.h>
#include <inttypes.h> /* for PRI* */
#include <stdio.h>    /* for fprintf(), stderr */
#include <string.h>   /* for strerror() */

#include <libcr/coroutine.h>
#include <libcr_ipc/chan.h>
#include <libcr_ipc/mutex.h>
#include <libcr_ipc/select.h>
#include <libnetio/netio.h>

#include <lib9p/srv.h>
#include "internal.h"

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

#define NAME  fidmap
#define KEY_T uint32_t
#define VAL_T struct lib9p_srv_file *
#define CAP   CONFIG_9P_MAX_FIDS
#include "map.h"

#define NAME  reqmap
#define KEY_T uint32_t
#define VAL_T struct _lib9p_srv_req *
#define CAP   CONFIG_9P_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;
	int                      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 reqmap            reqs;
	struct fidmap            fids;
};

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

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

#define nonrespond_errorf(fmt, ...) \
	fprintf(stderr, "error: " fmt "\n" __VA_OPT__(,) __VA_ARGS__)

static uint32_t rerror_overhead_for_version(enum lib9p_version version,
                                            uint8_t *scratch) {
	struct lib9p_ctx empty_ctx = {
		.version      = version,
		.max_msg_size = CONFIG_9P_MAX_MSG_SIZE,
	};
	struct lib9p_msg_Rerror empty_error = { 0 };
	bool e;

	e = lib9p_marshal(&empty_ctx, LIB9P_TYP_Rerror,
	                  0,            /* tag */
	                  &empty_error, /* host_body */
	                  scratch);      /* net_bytes */
	assert(!e);

	uint32_t min_msg_size = decode_u32le(scratch);

	/* Assert that min_msg_size + biggest_possible_MAX_ERR_SIZE
	 * won't overflow uint32... because using
	 * __builtin_add_overflow in respond_error() would be a bit
	 * much.  */
	assert(min_msg_size < (UINT32_MAX - UINT16_MAX));
	/* Assert that min_msg_size doesn't overflow MAX_MSG_SIZE.  */
	assert(CONFIG_9P_MAX_MSG_SIZE >= min_msg_size);

	return min_msg_size;
}

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

	ssize_t r;
	struct lib9p_msg_Rerror host = {
		.ename = {
			.len = strnlen(req->ctx.basectx.err_msg,
			               CONFIG_9P_MAX_ERR_SIZE),
			.utf8 = req->ctx.basectx.err_msg,
		},
		.errno = req->ctx.basectx.err_num,
	};

	struct _srv_sess *sess = req->parent_sess;

	/* Truncate the error-string if necessary to avoid needing to
	 * return ERANGE.  The assert() in
	 * rerror_overhead_for_version() has checked that this
	 * addition doesn't overflow.  */
	if (((uint32_t)host.ename.len) + sess->rerror_overhead > sess->max_msg_size)
		host.ename.len = sess->max_msg_size - sess->rerror_overhead;

	lib9p_marshal(&req->ctx.basectx, LIB9P_TYP_Rerror,
	              req->tag, &host, req->net_bytes);

	cr_mutex_lock(&sess->parent_conn->writelock);
	r = netio_write(sess->parent_conn->fd,
	                req->net_bytes, decode_u32le(req->net_bytes));
	cr_mutex_unlock(&sess->parent_conn->writelock);
	if (r < 0)
		nonrespond_errorf("write: %s", strerror(-r));
}

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

static bool read_at_least(int fd, uint8_t *buf, size_t goal, size_t *done) {
	assert(buf);
	assert(goal);
	assert(done);
	while (*done < goal) {
		ssize_t r = netio_read(fd, &buf[*done], CONFIG_9P_MAX_MSG_SIZE - *done);
		if (r < 0) {
			nonrespond_errorf("read: %s", 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);

COROUTINE lib9p_srv_read_cr(void *_srv) {
	uint8_t buf[CONFIG_9P_MAX_MSG_SIZE];

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

	uint32_t initial_rerror_overhead = rerror_overhead_for_version(0, buf);

	for (;;) {
		struct _srv_conn conn = {
			.parent_srv = srv,
			.fd         = netio_accept(srv->sockfd),
			.reader     = cr_getcid(),
		};
		if (conn.fd < 0) {
			nonrespond_errorf("accept: %s", strerror(-conn.fd));
			continue;
		}

		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;
			if (read_at_least(conn.fd, buf, 4, &done))
				goto close;
			size_t goal = decode_u32le(buf);
			if (goal < 7) {
				nonrespond_errorf("T-message is impossibly small");
				goto close;
			}
			if (read_at_least(conn.fd, buf, 7, &done))
				goto close;
			struct _lib9p_srv_req req = {
				.parent_sess = &sess,
				.tag         = decode_u16le(&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;
			}
			if (read_at_least(conn.fd, buf, goal, &done))
				goto close;

			/* Handle the message...  */
			if (buf[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:
		netio_close(conn.fd, true, sess.reqs.len == 0);
		if (sess.reqs.len) {
			sess.closing = true;
			cr_pause_and_yield();
			assert(sess.reqs.len == 0);
			netio_close(conn.fd, true, true);
		}
	}

	cr_end();
}

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

COROUTINE lib9p_srv_write_cr(void *_srv) {
	uint8_t                 net[CONFIG_9P_MAX_MSG_SIZE];
	struct _lib9p_srv_req   req;
	_lib9p_srv_reqch_req_t  rpc_handle;

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

	for (;;) {
		/* Receive the request from the reader coroutine.  ************/
		rpc_handle = _lib9p_srv_reqch_recv_req(&srv->_reqch);
		/* Deep-copy the request from the reader coroutine's
		 * stack to our stack.  */
		req = *rpc_handle.req;
		memcpy(net, req.net_bytes, decode_u32le(req.net_bytes));
		req.net_bytes = net;
		/* 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.  ****************************************/
		if (_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);
_HANDLER_PROTO(session); /* 9P2000.e */
_HANDLER_PROTO(sread);   /* 9P2000.e */
_HANDLER_PROTO(swrite);  /* 9P2000.e */

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,
	[LIB9P_TYP_Tsession] = (tmessage_handler)handle_Tsession, /* 9P2000.e */
	[LIB9P_TYP_Tsread]   = (tmessage_handler)handle_Tsread,   /* 9P2000.e */
	[LIB9P_TYP_Tswrite]  = (tmessage_handler)handle_Tswrite,  /* 9P2000.e */
};

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

	/* Unmarshal it.  */
	enum lib9p_msg_type typ = ctx->net_bytes[4];
	if (typ % 2 != 0) {
		lib9p_errorf(&ctx->ctx.basectx,
		             LINUX_EOPNOTSUPP, "expected a T-message but got an R-message: message_type=%s",
		             lib9p_msg_type_str(typ));
		goto write;
	}
	ssize_t host_size = lib9p_validate(&ctx->ctx.basectx, ctx->net_bytes);
	if (host_size < 0)
		goto write;
	if ((size_t)host_size > sizeof(host_req)) {
		lib9p_errorf(&ctx->ctx.basectx,
		             LINUX_EMSGSIZE, "unmarshalled payload larger than server limit (%zu > %zu)",
		             host_size, sizeof(host_req));
		goto write;
	}
	lib9p_unmarshal(&ctx->ctx.basectx, ctx->net_bytes,
	                &typ, &ctx->tag, 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 {
		if (lib9p_marshal(&ctx->ctx.basectx, typ+1, ctx->tag, host_resp,
		                  ctx->net_bytes))
			goto write;

		cr_mutex_lock(&ctx->parent_sess->parent_conn->writelock);
		netio_write(ctx->parent_sess->parent_conn->fd,
		            ctx->net_bytes, decode_u32le(ctx->net_bytes));
		cr_mutex_unlock(&ctx->parent_sess->parent_conn->writelock);
	}
}

static void handle_Tversion(struct _lib9p_srv_req *ctx,
                            struct lib9p_msg_Tversion *req,
                            struct lib9p_msg_Rversion *resp) {
	assert(ctx);
	assert(req);
	assert(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.utf8[6] == '\0' || req->version.utf8[6] == '.')) {
		if (strcmp((char *)&req->version.utf8[6], ".u") == 0)
			version = LIB9P_VER_9P2000_u;
		else if (strcmp((char *)&req->version.utf8[6], ".e") == 0)
			version = LIB9P_VER_9P2000_e;
		else
			version = LIB9P_VER_9P2000;
	}

	uint32_t min_msg_size = rerror_overhead_for_version(version, ctx->net_bytes);
	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;
	}

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdiscarded-qualifiers"
	resp->version.utf8 = lib9p_version_str(version);
#pragma GCC diagnostic pop
	resp->version.len = strlen((char *)resp->version.utf8);
	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 __attribute__((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 lib9p_srv_file **filepp __attribute__((unused));
		MAP_FOREACH(&ctx->parent_sess->fids, fid, filepp) {
			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) {
	assert(ctx);
	assert(req);
	assert(resp);

	ctx->ctx.uid = req->n_uname;
	ctx->ctx.uname = req->uname.utf8;
	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.utf8);
	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) {
	assert(ctx);
	assert(req);
	assert(resp);

	ctx->ctx.uid = req->n_uname;
	ctx->ctx.uname = req->uname.utf8;
	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 (strcmp(fh->data.auth.uname, req->uname.utf8) != 0)
			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, req->uname.utf8);
		else if (strcmp(fh->data.auth.aname, req->aname.utf8) != 0)
			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, 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 implemented");
		return;
	} else {
		if (req->afid != LIB9P_NOFID) {
			lib9p_error(&ctx->ctx.basectx,
			            LINUX_EACCES, "FID provided as auth-file, but no auth-file is required");
			return;
		}
	}

	if (fidmap_load(&ctx->parent_sess->fids, req->fid)) {
		lib9p_error(&ctx->ctx.basectx,
		            LINUX_EBADF, "FID already in use");
		return;
	}

	struct lib9p_srv_file *rootdir = srv->rootdir(&ctx->ctx, req->aname.utf8);
	assert((rootdir == NULL) == lib9p_ctx_has_error(&ctx->ctx.basectx));
	if (lib9p_ctx_has_error(&ctx->ctx.basectx))
		return;
	rootdir->_parent_dir = rootdir;

	if (!fidmap_store(&ctx->parent_sess->fids, req->fid, rootdir)) {
		lib9p_error(&ctx->ctx.basectx,
		            LINUX_EMFILE, "too many open files");
		rootdir->vtable->free(&ctx->ctx, rootdir);
		return;
	}

	struct lib9p_stat stat = rootdir->vtable->stat(&ctx->ctx, rootdir);
	if (lib9p_ctx_has_error(&ctx->ctx.basectx)) {
		handle_Tclunk(ctx,
		              &(struct lib9p_msg_Tclunk){.fid = req->fid},
		              &(struct lib9p_msg_Rclunk){});
		return;
	}


	resp->qid = stat.file_qid;
	return;
}

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

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

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

	struct lib9p_srv_file **dirpp = fidmap_load(&ctx->parent_sess->fids, req->fid);
	if (!dirpp) {
		lib9p_errorf(&ctx->ctx.basectx,
		             LINUX_EBADF, "bad file number %"PRIu32, req->fid);
		return;
	}
	if (req->newfid != req->fid && fidmap_load(&ctx->parent_sess->fids, req->newfid)) {
		lib9p_error(&ctx->ctx.basectx,
		            LINUX_EBADF, "FID already in use");
		return;
	}

	struct lib9p_srv_file *dirp;
	if (req->newfid == req->fid)
		dirp = *dirpp;
	else {
		dirp = (*dirpp)->vtable->clone(&ctx->ctx, *dirpp);
		assert((dirp == NULL) == lib9p_ctx_has_error(&ctx->ctx.basectx));
		if (lib9p_ctx_has_error(&ctx->ctx.basectx))
			return;
	}

	resp->wqid = (struct lib9p_qid *)(&resp[1]);
	for (resp->nwqid = 0; resp->nwqid < req->nwname; resp->nwqid++) {
		struct lib9p_srv_file *memberp;
		if (strcmp(req->wname[resp->nwqid].utf8, "..")) {
			memberp = dirp->_parent_dir;
		} else {
			struct lib9p_stat stat = dirp->vtable->stat(&ctx->ctx, dirp);
			if (lib9p_ctx_has_error(&ctx->ctx.basectx))
				break;
			if (!(stat.file_qid.type & LIB9P_QT_DIR) || !(stat.file_mode & LIB9P_DM_DIR)) {
				lib9p_error(&ctx->ctx.basectx,
				            LINUX_ENOTDIR, "not a directory");
				break;
			}

			memberp = dirp->vtable->dopen(&ctx->ctx, dirp, req->wname[resp->nwqid].utf8);
			assert((memberp == NULL) == lib9p_ctx_has_error(&ctx->ctx.basectx));
			if (lib9p_ctx_has_error(&ctx->ctx.basectx))
				break;
		}

		struct lib9p_stat stat = memberp->vtable->stat(&ctx->ctx, memberp);
		if (lib9p_ctx_has_error(&ctx->ctx.basectx))
			break;
		resp->wqid[resp->nwqid] = stat.file_qid;

		dirp->vtable->free(&ctx->ctx, dirp);
		dirp = memberp;
	}
	if (resp->nwqid == req->nwname) {
		if (req->newfid == req->fid)
			*dirpp = dirp;
		else
			if (!fidmap_store(&ctx->parent_sess->fids, req->newfid, dirp)) {
				lib9p_error(&ctx->ctx.basectx,
				            LINUX_EMFILE, "too many open files");
				dirp->vtable->free(&ctx->ctx, dirp);
			}
	} else if (resp->nwqid > 0) {
		ctx->ctx.basectx.err_num = 0;
		ctx->ctx.basectx.err_msg[0] = '\0';
	}
}

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

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

static void handle_Tcreate(struct _lib9p_srv_req *ctx,
                           struct lib9p_msg_Tcreate *req,
                           struct lib9p_msg_Rcreate *resp) {
	assert(ctx);
	assert(req);
	assert(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) {
	assert(ctx);
	assert(req);
	assert(resp);

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

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

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

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

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

	(*filepp)->vtable->free(&ctx->ctx, *filepp);
	fidmap_del(&ctx->parent_sess->fids, req->fid);
}

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

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

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

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

	resp->stat = (*filepp)->vtable->stat(&ctx->ctx, *filepp);
}

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

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

static void handle_Tsession(struct _lib9p_srv_req *ctx,
                            struct lib9p_msg_Tsession *req,
                            struct lib9p_msg_Rsession *resp) {
	assert(ctx);
	assert(req);
	assert(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) {
	assert(ctx);
	assert(req);
	assert(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) {
	assert(ctx);
	assert(req);
	assert(resp);

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