aboutsummaryrefslogtreecommitdiff
path: root/request.c
diff options
context:
space:
mode:
authorJakub Wojciech Klama <jceel@FreeBSD.org>2020-04-29 16:24:32 +0000
committerJakub Wojciech Klama <jceel@FreeBSD.org>2020-04-29 16:24:32 +0000
commit6d9daf7da2883bacd006132f8a1b6a7c388e191c (patch)
tree0a8368af46b0e674f120a4dde9a1609648f59c07 /request.c
downloadsrc-6d9daf7da2883bacd006132f8a1b6a7c388e191c.tar.gz
src-6d9daf7da2883bacd006132f8a1b6a7c388e191c.zip
Notes
Diffstat (limited to 'request.c')
-rw-r--r--request.c1440
1 files changed, 1440 insertions, 0 deletions
diff --git a/request.c b/request.c
new file mode 100644
index 000000000000..357bd23bf98a
--- /dev/null
+++ b/request.c
@@ -0,0 +1,1440 @@
+/*
+ * Copyright 2016 Jakub Klama <jceel@FreeBSD.org>
+ * All rights reserved
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted providing 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.
+ *
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+#include <sys/param.h>
+#include <sys/uio.h>
+#if defined(__FreeBSD__)
+#include <sys/sbuf.h>
+#else
+#include "sbuf/sbuf.h"
+#endif
+#include "lib9p.h"
+#include "lib9p_impl.h"
+#include "fcall.h"
+#include "fid.h"
+#include "hashtable.h"
+#include "log.h"
+#include "linux_errno.h"
+#include "backend/backend.h"
+#include "threadpool.h"
+
+#define N(x) (sizeof(x) / sizeof(x[0]))
+
+static int l9p_dispatch_tversion(struct l9p_request *req);
+static int l9p_dispatch_tattach(struct l9p_request *req);
+static int l9p_dispatch_tclunk(struct l9p_request *req);
+static int l9p_dispatch_tcreate(struct l9p_request *req);
+static int l9p_dispatch_topen(struct l9p_request *req);
+static int l9p_dispatch_tread(struct l9p_request *req);
+static int l9p_dispatch_tremove(struct l9p_request *req);
+static int l9p_dispatch_tstat(struct l9p_request *req);
+static int l9p_dispatch_twalk(struct l9p_request *req);
+static int l9p_dispatch_twrite(struct l9p_request *req);
+static int l9p_dispatch_twstat(struct l9p_request *req);
+static int l9p_dispatch_tstatfs(struct l9p_request *req);
+static int l9p_dispatch_tlopen(struct l9p_request *req);
+static int l9p_dispatch_tlcreate(struct l9p_request *req);
+static int l9p_dispatch_tsymlink(struct l9p_request *req);
+static int l9p_dispatch_tmknod(struct l9p_request *req);
+static int l9p_dispatch_trename(struct l9p_request *req);
+static int l9p_dispatch_treadlink(struct l9p_request *req);
+static int l9p_dispatch_tgetattr(struct l9p_request *req);
+static int l9p_dispatch_tsetattr(struct l9p_request *req);
+static int l9p_dispatch_txattrwalk(struct l9p_request *req);
+static int l9p_dispatch_txattrcreate(struct l9p_request *req);
+static int l9p_dispatch_treaddir(struct l9p_request *req);
+static int l9p_dispatch_tfsync(struct l9p_request *req);
+static int l9p_dispatch_tlock(struct l9p_request *req);
+static int l9p_dispatch_tgetlock(struct l9p_request *req);
+static int l9p_dispatch_tlink(struct l9p_request *req);
+static int l9p_dispatch_tmkdir(struct l9p_request *req);
+static int l9p_dispatch_trenameat(struct l9p_request *req);
+static int l9p_dispatch_tunlinkat(struct l9p_request *req);
+
+/*
+ * Each Txxx handler has a "must run" flag. If it is false,
+ * we check for a flush request before calling the handler.
+ * If a flush is already requested we can instantly fail the
+ * request with EINTR.
+ *
+ * Tclunk and Tremove must run because they make their fids
+ * become invalid. Tversion and Tattach should never get
+ * a flush request applied (it makes no sense as the connection
+ * is not really running yet), so it should be harmless to
+ * set them either way, but for now we have them as must-run.
+ * Flushing a Tflush is not really allowed either so we keep
+ * these as must-run too (although they run without being done
+ * threaded anyway).
+ */
+struct l9p_handler {
+ enum l9p_ftype type;
+ int (*handler)(struct l9p_request *);
+ bool must_run;
+};
+
+static const struct l9p_handler l9p_handlers_no_version[] = {
+ {L9P_TVERSION, l9p_dispatch_tversion, true},
+};
+
+static const struct l9p_handler l9p_handlers_base[] = {
+ {L9P_TVERSION, l9p_dispatch_tversion, true},
+ {L9P_TATTACH, l9p_dispatch_tattach, true},
+ {L9P_TCLUNK, l9p_dispatch_tclunk, true},
+ {L9P_TFLUSH, l9p_threadpool_tflush, true},
+ {L9P_TCREATE, l9p_dispatch_tcreate, false},
+ {L9P_TOPEN, l9p_dispatch_topen, false},
+ {L9P_TREAD, l9p_dispatch_tread, false},
+ {L9P_TWRITE, l9p_dispatch_twrite, false},
+ {L9P_TREMOVE, l9p_dispatch_tremove, true},
+ {L9P_TSTAT, l9p_dispatch_tstat, false},
+ {L9P_TWALK, l9p_dispatch_twalk, false},
+ {L9P_TWSTAT, l9p_dispatch_twstat, false}
+};
+static const struct l9p_handler l9p_handlers_dotu[] = {
+ {L9P_TVERSION, l9p_dispatch_tversion, true},
+ {L9P_TATTACH, l9p_dispatch_tattach, true},
+ {L9P_TCLUNK, l9p_dispatch_tclunk, true},
+ {L9P_TFLUSH, l9p_threadpool_tflush, true},
+ {L9P_TCREATE, l9p_dispatch_tcreate, false},
+ {L9P_TOPEN, l9p_dispatch_topen, false},
+ {L9P_TREAD, l9p_dispatch_tread, false},
+ {L9P_TWRITE, l9p_dispatch_twrite, false},
+ {L9P_TREMOVE, l9p_dispatch_tremove, true},
+ {L9P_TSTAT, l9p_dispatch_tstat, false},
+ {L9P_TWALK, l9p_dispatch_twalk, false},
+ {L9P_TWSTAT, l9p_dispatch_twstat, false}
+};
+static const struct l9p_handler l9p_handlers_dotL[] = {
+ {L9P_TVERSION, l9p_dispatch_tversion, true},
+ {L9P_TATTACH, l9p_dispatch_tattach, true},
+ {L9P_TCLUNK, l9p_dispatch_tclunk, true},
+ {L9P_TFLUSH, l9p_threadpool_tflush, true},
+ {L9P_TCREATE, l9p_dispatch_tcreate, false},
+ {L9P_TOPEN, l9p_dispatch_topen, false},
+ {L9P_TREAD, l9p_dispatch_tread, false},
+ {L9P_TWRITE, l9p_dispatch_twrite, false},
+ {L9P_TREMOVE, l9p_dispatch_tremove, true},
+ {L9P_TSTAT, l9p_dispatch_tstat, false},
+ {L9P_TWALK, l9p_dispatch_twalk, false},
+ {L9P_TWSTAT, l9p_dispatch_twstat, false},
+ {L9P_TSTATFS, l9p_dispatch_tstatfs, false},
+ {L9P_TLOPEN, l9p_dispatch_tlopen, false},
+ {L9P_TLCREATE, l9p_dispatch_tlcreate, false},
+ {L9P_TSYMLINK, l9p_dispatch_tsymlink, false},
+ {L9P_TMKNOD, l9p_dispatch_tmknod, false},
+ {L9P_TRENAME, l9p_dispatch_trename, false},
+ {L9P_TREADLINK, l9p_dispatch_treadlink, false},
+ {L9P_TGETATTR, l9p_dispatch_tgetattr, false},
+ {L9P_TSETATTR, l9p_dispatch_tsetattr, false},
+ {L9P_TXATTRWALK, l9p_dispatch_txattrwalk, false},
+ {L9P_TXATTRCREATE, l9p_dispatch_txattrcreate, false},
+ {L9P_TREADDIR, l9p_dispatch_treaddir, false},
+ {L9P_TFSYNC, l9p_dispatch_tfsync, false},
+ {L9P_TLOCK, l9p_dispatch_tlock, true},
+ {L9P_TGETLOCK, l9p_dispatch_tgetlock, true},
+ {L9P_TLINK, l9p_dispatch_tlink, false},
+ {L9P_TMKDIR, l9p_dispatch_tmkdir, false},
+ {L9P_TRENAMEAT, l9p_dispatch_trenameat, false},
+ {L9P_TUNLINKAT, l9p_dispatch_tunlinkat, false},
+};
+
+/*
+ * NB: version index 0 is reserved for new connections, and
+ * is a protocol that handles only L9P_TVERSION. Once we get a
+ * valid version, we start a new session using its dispatch table.
+ */
+static const struct {
+ const char *name;
+ const struct l9p_handler *handlers;
+ int n_handlers;
+} l9p_versions[] = {
+ { "<none>", l9p_handlers_no_version, N(l9p_handlers_no_version) },
+ { "9P2000", l9p_handlers_base, N(l9p_handlers_base) },
+ { "9P2000.u", l9p_handlers_dotu, N(l9p_handlers_dotu), },
+ { "9P2000.L", l9p_handlers_dotL, N(l9p_handlers_dotL), },
+};
+
+/*
+ * Run the appropriate handler for this request.
+ * It's our caller's responsibility to respond.
+ */
+int
+l9p_dispatch_request(struct l9p_request *req)
+{
+ struct l9p_connection *conn;
+#if defined(L9P_DEBUG)
+ struct sbuf *sb;
+#endif
+ size_t i, n;
+ const struct l9p_handler *handlers, *hp;
+ bool flush_requested;
+
+ conn = req->lr_conn;
+ flush_requested = req->lr_flushstate == L9P_FLUSH_REQUESTED_PRE_START;
+
+ handlers = l9p_versions[conn->lc_version].handlers;
+ n = (size_t)l9p_versions[conn->lc_version].n_handlers;
+ for (hp = handlers, i = 0; i < n; hp++, i++)
+ if (req->lr_req.hdr.type == hp->type)
+ goto found;
+ hp = NULL;
+found:
+
+#if defined(L9P_DEBUG)
+ sb = sbuf_new_auto();
+ if (flush_requested) {
+ sbuf_cat(sb, "FLUSH requested pre-dispatch");
+ if (hp != NULL && hp->must_run)
+ sbuf_cat(sb, ", but must run");
+ sbuf_cat(sb, ": ");
+ }
+ l9p_describe_fcall(&req->lr_req, conn->lc_version, sb);
+ sbuf_finish(sb);
+
+ L9P_LOG(L9P_DEBUG, "%s", sbuf_data(sb));
+ sbuf_delete(sb);
+#endif
+
+ if (hp != NULL) {
+ if (!flush_requested || hp->must_run)
+ return (hp->handler(req));
+ return (EINTR);
+ }
+
+ L9P_LOG(L9P_WARNING, "unknown request of type %d",
+ req->lr_req.hdr.type);
+ return (ENOSYS);
+}
+
+/*
+ * Translate BSD errno to 9P2000/9P2000.u errno.
+ */
+static inline int
+e29p(int errnum)
+{
+ static int const table[] = {
+ [ENOTEMPTY] = EPERM,
+ [EDQUOT] = EPERM,
+ [ENOSYS] = EPERM, /* ??? */
+ };
+
+ if ((size_t)errnum < N(table) && table[errnum] != 0)
+ return (table[errnum]);
+ if (errnum <= ERANGE)
+ return (errnum);
+ return (EIO); /* ??? */
+}
+
+/*
+ * Translate BSD errno to Linux errno.
+ */
+static inline int
+e2linux(int errnum)
+{
+ static int const table[] = {
+ [EDEADLK] = LINUX_EDEADLK,
+ [EAGAIN] = LINUX_EAGAIN,
+ [EINPROGRESS] = LINUX_EINPROGRESS,
+ [EALREADY] = LINUX_EALREADY,
+ [ENOTSOCK] = LINUX_ENOTSOCK,
+ [EDESTADDRREQ] = LINUX_EDESTADDRREQ,
+ [EMSGSIZE] = LINUX_EMSGSIZE,
+ [EPROTOTYPE] = LINUX_EPROTOTYPE,
+ [ENOPROTOOPT] = LINUX_ENOPROTOOPT,
+ [EPROTONOSUPPORT] = LINUX_EPROTONOSUPPORT,
+ [ESOCKTNOSUPPORT] = LINUX_ESOCKTNOSUPPORT,
+ [EOPNOTSUPP] = LINUX_EOPNOTSUPP,
+ [EPFNOSUPPORT] = LINUX_EPFNOSUPPORT,
+ [EAFNOSUPPORT] = LINUX_EAFNOSUPPORT,
+ [EADDRINUSE] = LINUX_EADDRINUSE,
+ [EADDRNOTAVAIL] = LINUX_EADDRNOTAVAIL,
+ [ENETDOWN] = LINUX_ENETDOWN,
+ [ENETUNREACH] = LINUX_ENETUNREACH,
+ [ENETRESET] = LINUX_ENETRESET,
+ [ECONNABORTED] = LINUX_ECONNABORTED,
+ [ECONNRESET] = LINUX_ECONNRESET,
+ [ENOBUFS] = LINUX_ENOBUFS,
+ [EISCONN] = LINUX_EISCONN,
+ [ENOTCONN] = LINUX_ENOTCONN,
+ [ESHUTDOWN] = LINUX_ESHUTDOWN,
+ [ETOOMANYREFS] = LINUX_ETOOMANYREFS,
+ [ETIMEDOUT] = LINUX_ETIMEDOUT,
+ [ECONNREFUSED] = LINUX_ECONNREFUSED,
+ [ELOOP] = LINUX_ELOOP,
+ [ENAMETOOLONG] = LINUX_ENAMETOOLONG,
+ [EHOSTDOWN] = LINUX_EHOSTDOWN,
+ [EHOSTUNREACH] = LINUX_EHOSTUNREACH,
+ [ENOTEMPTY] = LINUX_ENOTEMPTY,
+ [EPROCLIM] = LINUX_EAGAIN,
+ [EUSERS] = LINUX_EUSERS,
+ [EDQUOT] = LINUX_EDQUOT,
+ [ESTALE] = LINUX_ESTALE,
+ [EREMOTE] = LINUX_EREMOTE,
+ /* EBADRPC = unmappable? */
+ /* ERPCMISMATCH = unmappable? */
+ /* EPROGUNAVAIL = unmappable? */
+ /* EPROGMISMATCH = unmappable? */
+ /* EPROCUNAVAIL = unmappable? */
+ [ENOLCK] = LINUX_ENOLCK,
+ [ENOSYS] = LINUX_ENOSYS,
+ /* EFTYPE = unmappable? */
+ /* EAUTH = unmappable? */
+ /* ENEEDAUTH = unmappable? */
+ [EIDRM] = LINUX_EIDRM,
+ [ENOMSG] = LINUX_ENOMSG,
+ [EOVERFLOW] = LINUX_EOVERFLOW,
+ [ECANCELED] = LINUX_ECANCELED,
+ [EILSEQ] = LINUX_EILSEQ,
+ /* EDOOFUS = unmappable? */
+ [EBADMSG] = LINUX_EBADMSG,
+ [EMULTIHOP] = LINUX_EMULTIHOP,
+ [ENOLINK] = LINUX_ENOLINK,
+ [EPROTO] = LINUX_EPROTO,
+ /* ENOTCAPABLE = unmappable? */
+#ifdef ECAPMODE
+ [ECAPMODE] = EPERM,
+#endif
+#ifdef ENOTRECOVERABLE
+ [ENOTRECOVERABLE] = LINUX_ENOTRECOVERABLE,
+#endif
+#ifdef EOWNERDEAD
+ [EOWNERDEAD] = LINUX_EOWNERDEAD,
+#endif
+ };
+
+ /*
+ * In case we want to return a raw Linux errno, allow negative
+ * values a la Linux kernel internals.
+ *
+ * Values up to ERANGE are shared across systems (see
+ * linux_errno.h), except for EAGAIN.
+ */
+ if (errnum < 0)
+ return (-errnum);
+
+ if ((size_t)errnum < N(table) && table[errnum] != 0)
+ return (table[errnum]);
+
+ if (errnum <= ERANGE)
+ return (errnum);
+
+ L9P_LOG(L9P_WARNING, "cannot map errno %d to anything reasonable",
+ errnum);
+
+ return (LINUX_ENOTRECOVERABLE); /* ??? */
+}
+
+/*
+ * Send response to request, or possibly just drop request.
+ * We also need to know whether to remove the request from
+ * the tag hash table.
+ */
+void
+l9p_respond(struct l9p_request *req, bool drop, bool rmtag)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ size_t iosize;
+#if defined(L9P_DEBUG)
+ struct sbuf *sb;
+ const char *ftype;
+#endif
+ int error;
+
+ req->lr_resp.hdr.tag = req->lr_req.hdr.tag;
+
+ error = req->lr_error;
+ if (error == 0)
+ req->lr_resp.hdr.type = req->lr_req.hdr.type + 1;
+ else {
+ if (conn->lc_version == L9P_2000L) {
+ req->lr_resp.hdr.type = L9P_RLERROR;
+ req->lr_resp.error.errnum = (uint32_t)e2linux(error);
+ } else {
+ req->lr_resp.hdr.type = L9P_RERROR;
+ req->lr_resp.error.ename = strerror(error);
+ req->lr_resp.error.errnum = (uint32_t)e29p(error);
+ }
+ }
+
+#if defined(L9P_DEBUG)
+ sb = sbuf_new_auto();
+ l9p_describe_fcall(&req->lr_resp, conn->lc_version, sb);
+ sbuf_finish(sb);
+
+ switch (req->lr_flushstate) {
+ case L9P_FLUSH_NONE:
+ ftype = "";
+ break;
+ case L9P_FLUSH_REQUESTED_PRE_START:
+ ftype = "FLUSH requested pre-dispatch: ";
+ break;
+ case L9P_FLUSH_REQUESTED_POST_START:
+ ftype = "FLUSH requested while running: ";
+ break;
+ case L9P_FLUSH_TOOLATE:
+ ftype = "FLUSH requested too late: ";
+ break;
+ }
+ L9P_LOG(L9P_DEBUG, "%s%s%s",
+ drop ? "DROP: " : "", ftype, sbuf_data(sb));
+ sbuf_delete(sb);
+#endif
+
+ error = drop ? 0 :
+ l9p_pufcall(&req->lr_resp_msg, &req->lr_resp, conn->lc_version);
+ if (rmtag)
+ ht_remove(&conn->lc_requests, req->lr_req.hdr.tag);
+ if (error != 0) {
+ L9P_LOG(L9P_ERROR, "cannot pack response");
+ drop = true;
+ }
+
+ if (drop) {
+ conn->lc_lt.lt_drop_response(req,
+ req->lr_resp_msg.lm_iov, req->lr_resp_msg.lm_niov,
+ conn->lc_lt.lt_aux);
+ } else {
+ iosize = req->lr_resp_msg.lm_size;
+
+ /*
+ * Include I/O size in calculation for Rread and
+ * Rreaddir responses.
+ */
+ if (req->lr_resp.hdr.type == L9P_RREAD ||
+ req->lr_resp.hdr.type == L9P_RREADDIR)
+ iosize += req->lr_resp.io.count;
+
+ conn->lc_lt.lt_send_response(req,
+ req->lr_resp_msg.lm_iov, req->lr_resp_msg.lm_niov,
+ iosize, conn->lc_lt.lt_aux);
+ }
+
+ l9p_freefcall(&req->lr_req);
+ l9p_freefcall(&req->lr_resp);
+
+ free(req);
+}
+
+/*
+ * This allows a caller to iterate through the data in a
+ * read or write request (creating the data if packing,
+ * scanning through it if unpacking). This is used for
+ * writing readdir entries, so mode should be L9P_PACK
+ * (but we allow L9P_UNPACK so that debug code can also scan
+ * through the data later, if desired).
+ *
+ * This relies on the Tread op having positioned the request's
+ * iov to the beginning of the data buffer (note the l9p_seek_iov
+ * in l9p_dispatch_tread).
+ */
+void
+l9p_init_msg(struct l9p_message *msg, struct l9p_request *req,
+ enum l9p_pack_mode mode)
+{
+
+ msg->lm_size = 0;
+ msg->lm_mode = mode;
+ msg->lm_cursor_iov = 0;
+ msg->lm_cursor_offset = 0;
+ msg->lm_niov = req->lr_data_niov;
+ memcpy(msg->lm_iov, req->lr_data_iov,
+ sizeof (struct iovec) * req->lr_data_niov);
+}
+
+enum fid_lookup_flags {
+ F_REQUIRE_OPEN = 0x01, /* require that the file be marked OPEN */
+ F_REQUIRE_DIR = 0x02, /* require that the file be marked ISDIR */
+ F_REQUIRE_XATTR = 0x04, /* require that the file be marked XATTR */
+ F_REQUIRE_AUTH = 0x08, /* require that the fid be marked AUTH */
+ F_FORBID_OPEN = 0x10, /* forbid that the file be marked OPEN */
+ F_FORBID_DIR = 0x20, /* forbid that the file be marked ISDIR */
+ F_FORBID_XATTR = 0x40, /* forbid that the file be marked XATTR */
+ F_ALLOW_AUTH = 0x80, /* allow that the fid be marked AUTH */
+};
+
+/*
+ * Look up a fid. It must correspond to a valid file, else we return
+ * the given errno (some "not a valid fid" calls must return EIO and
+ * some must return EINVAL and qemu returns ENOENT in other cases and
+ * so on, so we just provide a general "return this error number").
+ *
+ * Callers may also set constraints: fid must be (or not be) open,
+ * must be (or not be) a directory, must be (or not be) an xattr.
+ *
+ * Only one op has a fid that *must* be an auth fid. Most ops forbid
+ * auth fids So instead of FORBID we have ALLOW here and the default
+ * is FORBID.
+ */
+static inline int
+fid_lookup(struct l9p_connection *conn, uint32_t fid, int err, int flags,
+ struct l9p_fid **afile)
+{
+ struct l9p_fid *file;
+
+ file = ht_find(&conn->lc_files, fid);
+ if (file == NULL)
+ return (err);
+
+ /*
+ * As soon as we go multithreaded / async, this
+ * assert has to become "return EINVAL" or "return err".
+ *
+ * We may also need a way to mark a fid as
+ * "in async op" (valid for some purposes, but cannot be
+ * used elsewhere until async op is completed or aborted).
+ *
+ * For now, this serves for bug-detecting.
+ */
+ assert(l9p_fid_isvalid(file));
+
+ /*
+ * Note that we're inline expanded and flags is constant,
+ * so unnecessary tests just drop out entirely.
+ */
+ if ((flags & F_REQUIRE_OPEN) && !l9p_fid_isopen(file))
+ return (EINVAL);
+ if ((flags & F_FORBID_OPEN) && l9p_fid_isopen(file))
+ return (EINVAL);
+ if ((flags & F_REQUIRE_DIR) && !l9p_fid_isdir(file))
+ return (ENOTDIR);
+ if ((flags & F_FORBID_DIR) && l9p_fid_isdir(file))
+ return (EISDIR);
+ if ((flags & F_REQUIRE_XATTR) && !l9p_fid_isxattr(file))
+ return (EINVAL);
+ if ((flags & F_FORBID_XATTR) && l9p_fid_isxattr(file))
+ return (EINVAL);
+ if (l9p_fid_isauth(file)) {
+ if ((flags & (F_REQUIRE_AUTH | F_ALLOW_AUTH)) == 0)
+ return (EINVAL);
+ } else if (flags & F_REQUIRE_AUTH)
+ return (EINVAL);
+ *afile = file;
+ return (0);
+}
+
+/*
+ * Append variable-size stat object and adjust io count.
+ * Returns 0 if the entire stat object was packed, -1 if not.
+ * A fully packed object updates the request's io count.
+ *
+ * Caller must use their own private l9p_message object since
+ * a partially packed object will leave the message object in
+ * a useless state.
+ *
+ * Frees the stat object.
+ */
+int
+l9p_pack_stat(struct l9p_message *msg, struct l9p_request *req,
+ struct l9p_stat *st)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ uint16_t size = l9p_sizeof_stat(st, conn->lc_version);
+ int ret = 0;
+
+ assert(msg->lm_mode == L9P_PACK);
+
+ if (req->lr_resp.io.count + size > req->lr_req.io.count ||
+ l9p_pustat(msg, st, conn->lc_version) < 0)
+ ret = -1;
+ else
+ req->lr_resp.io.count += size;
+ l9p_freestat(st);
+ return (ret);
+}
+
+static int
+l9p_dispatch_tversion(struct l9p_request *req)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ struct l9p_server *server = conn->lc_server;
+ enum l9p_version remote_version = L9P_INVALID_VERSION;
+ size_t i;
+ const char *remote_version_name;
+
+ for (i = 0; i < N(l9p_versions); i++) {
+ if (strcmp(req->lr_req.version.version,
+ l9p_versions[i].name) == 0) {
+ remote_version = (enum l9p_version)i;
+ break;
+ }
+ }
+
+ if (remote_version == L9P_INVALID_VERSION) {
+ L9P_LOG(L9P_ERROR, "unsupported remote version: %s",
+ req->lr_req.version.version);
+ return (ENOSYS);
+ }
+
+ remote_version_name = l9p_versions[remote_version].name;
+ L9P_LOG(L9P_INFO, "remote version: %s", remote_version_name);
+ L9P_LOG(L9P_INFO, "local version: %s",
+ l9p_versions[server->ls_max_version].name);
+
+ conn->lc_version = MIN(remote_version, server->ls_max_version);
+ conn->lc_msize = MIN(req->lr_req.version.msize, conn->lc_msize);
+ conn->lc_max_io_size = conn->lc_msize - 24;
+ req->lr_resp.version.version = strdup(remote_version_name);
+ req->lr_resp.version.msize = conn->lc_msize;
+ return (0);
+}
+
+static int
+l9p_dispatch_tattach(struct l9p_request *req)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ struct l9p_backend *be;
+ struct l9p_fid *fid;
+ int error;
+
+ /*
+ * We still don't have Tauth yet, but let's code this part
+ * anyway.
+ *
+ * Look up the auth fid first since if it fails we can just
+ * return immediately.
+ */
+ if (req->lr_req.tattach.afid != L9P_NOFID) {
+ error = fid_lookup(conn, req->lr_req.tattach.afid, EINVAL,
+ F_REQUIRE_AUTH, &req->lr_fid2);
+ if (error)
+ return (error);
+ } else
+ req->lr_fid2 = NULL;
+
+ fid = l9p_connection_alloc_fid(conn, req->lr_req.hdr.fid);
+ if (fid == NULL)
+ return (EINVAL);
+
+ be = conn->lc_server->ls_backend;
+
+ req->lr_fid = fid;
+
+ /* For backend convenience, set NONUNAME on 9P2000. */
+ if (conn->lc_version == L9P_2000)
+ req->lr_req.tattach.n_uname = L9P_NONUNAME;
+ error = be->attach(be->softc, req);
+
+ /*
+ * On success, fid becomes valid; on failure, disconnect.
+ * It certainly *should* be a directory here...
+ */
+ if (error == 0) {
+ l9p_fid_setvalid(fid);
+ if (req->lr_resp.rattach.qid.type & L9P_QTDIR)
+ l9p_fid_setdir(fid);
+ } else
+ l9p_connection_remove_fid(conn, fid);
+ return (error);
+}
+
+static int
+l9p_dispatch_tclunk(struct l9p_request *req)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ struct l9p_backend *be;
+ struct l9p_fid *fid;
+ int error;
+
+ /* Note that clunk is the only way to dispose of an auth fid. */
+ error = fid_lookup(conn, req->lr_req.hdr.fid, ENOENT,
+ F_ALLOW_AUTH, &fid);
+ if (error)
+ return (error);
+
+ be = conn->lc_server->ls_backend;
+ l9p_fid_unsetvalid(fid);
+
+ /*
+ * If it's an xattr fid there must, by definition, be an
+ * xattrclunk. The xattrclunk function can only be NULL if
+ * xattrwalk and xattrcreate are NULL or always return error.
+ *
+ * Q: do we want to allow async xattrclunk in case of very
+ * large xattr create? This will make things difficult,
+ * so probably not.
+ */
+ if (l9p_fid_isxattr(fid))
+ error = be->xattrclunk(be->softc, fid);
+ else
+ error = be->clunk(be->softc, fid);
+
+ /* fid is now gone regardless of any error return */
+ l9p_connection_remove_fid(conn, fid);
+ return (error);
+}
+
+static int
+l9p_dispatch_tcreate(struct l9p_request *req)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ struct l9p_backend *be;
+ uint32_t dmperm;
+ int error;
+
+ /* Incoming fid must represent a directory that has not been opened. */
+ error = fid_lookup(conn, req->lr_req.hdr.fid, EINVAL,
+ F_REQUIRE_DIR | F_FORBID_OPEN, &req->lr_fid);
+ if (error)
+ return (error);
+
+ be = conn->lc_server->ls_backend;
+ dmperm = req->lr_req.tcreate.perm;
+#define MKDIR_OR_SIMILAR \
+ (L9P_DMDIR | L9P_DMSYMLINK | L9P_DMNAMEDPIPE | L9P_DMSOCKET | L9P_DMDEVICE)
+
+ /*
+ * TODO:
+ * - check new file name
+ * - break out different kinds of create (file vs mkdir etc)
+ * - add async file-create (leaves req->lr_fid in limbo)
+ *
+ * A successful file-create changes the fid into an open file.
+ */
+ error = be->create(be->softc, req);
+ if (error == 0 && (dmperm & MKDIR_OR_SIMILAR) == 0) {
+ l9p_fid_unsetdir(req->lr_fid);
+ l9p_fid_setopen(req->lr_fid);
+ }
+
+ return (error);
+}
+
+static int
+l9p_dispatch_topen(struct l9p_request *req)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ struct l9p_backend *be;
+ int error;
+
+ error = fid_lookup(conn, req->lr_req.hdr.fid, ENOENT,
+ F_FORBID_OPEN | F_FORBID_XATTR, &req->lr_fid);
+ if (error)
+ return (error);
+
+ be = conn->lc_server->ls_backend;
+
+ /*
+ * TODO:
+ * - add async open (leaves req->lr_fid in limbo)
+ */
+ error = be->open(be->softc, req);
+ if (error == 0)
+ l9p_fid_setopen(req->lr_fid);
+ return (error);
+}
+
+static int
+l9p_dispatch_tread(struct l9p_request *req)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ struct l9p_backend *be;
+ struct l9p_fid *fid;
+ int error;
+
+ /* Xattr fids are not open, so we need our own tests. */
+ error = fid_lookup(conn, req->lr_req.hdr.fid, EINVAL, 0, &req->lr_fid);
+ if (error)
+ return (error);
+
+ /*
+ * Adjust so that writing messages (packing data) starts
+ * right after the count field in the response.
+ *
+ * size[4] + Rread[1] + tag[2] + count[4] = 11
+ */
+ l9p_seek_iov(req->lr_resp_msg.lm_iov, req->lr_resp_msg.lm_niov,
+ req->lr_data_iov, &req->lr_data_niov, 11);
+
+ /*
+ * If it's an xattr fid there must, by definition, be an
+ * xattrread. The xattrread function can only be NULL if
+ * xattrwalk and xattrcreate are NULL or always return error.
+ *
+ * TODO:
+ * separate out directory-read
+ * allow async read
+ */
+ be = conn->lc_server->ls_backend;
+ fid = req->lr_fid;
+ if (l9p_fid_isxattr(fid)) {
+ error = be->xattrread(be->softc, req);
+ } else if (l9p_fid_isopen(fid)) {
+ error = be->read(be->softc, req);
+ } else {
+ error = EINVAL;
+ }
+
+ return (error);
+}
+
+static int
+l9p_dispatch_tremove(struct l9p_request *req)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ struct l9p_backend *be;
+ struct l9p_fid *fid;
+ int error;
+
+ /*
+ * ?? Should we allow Tremove on auth fids? If so, do
+ * we pretend it is just a Tclunk?
+ */
+ error = fid_lookup(conn, req->lr_req.hdr.fid, EINVAL, 0, &fid);
+ if (error)
+ return (error);
+
+ be = conn->lc_server->ls_backend;
+ l9p_fid_unsetvalid(fid);
+
+ error = be->remove(be->softc, fid);
+ /* fid is now gone regardless of any error return */
+ l9p_connection_remove_fid(conn, fid);
+ return (error);
+}
+
+static int
+l9p_dispatch_tstat(struct l9p_request *req)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ struct l9p_backend *be;
+ struct l9p_fid *fid;
+ int error;
+
+ /* Allow Tstat on auth fid? Seems harmless enough... */
+ error = fid_lookup(conn, req->lr_req.hdr.fid, ENOENT,
+ F_ALLOW_AUTH, &fid);
+ if (error)
+ return (error);
+
+ be = conn->lc_server->ls_backend;
+ req->lr_fid = fid;
+ error = be->stat(be->softc, req);
+
+ if (error == 0) {
+ if (l9p_fid_isauth(fid))
+ req->lr_resp.rstat.stat.qid.type |= L9P_QTAUTH;
+
+ /* should we check req->lr_resp.rstat.qid.type L9P_QTDIR bit? */
+ if (req->lr_resp.rstat.stat.qid.type &= L9P_QTDIR)
+ l9p_fid_setdir(fid);
+ else
+ l9p_fid_unsetdir(fid);
+ }
+
+ return (error);
+}
+
+static int
+l9p_dispatch_twalk(struct l9p_request *req)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ struct l9p_backend *be;
+ struct l9p_fid *fid, *newfid;
+ uint16_t n;
+ int error;
+
+ /* Can forbid XATTR, but cannot require DIR. */
+ error = fid_lookup(conn, req->lr_req.hdr.fid, ENOENT,
+ F_FORBID_XATTR, &fid);
+ if (error)
+ return (error);
+
+ if (req->lr_req.twalk.hdr.fid != req->lr_req.twalk.newfid) {
+ newfid = l9p_connection_alloc_fid(conn,
+ req->lr_req.twalk.newfid);
+ if (newfid == NULL)
+ return (EINVAL);
+ } else
+ newfid = fid;
+
+ be = conn->lc_server->ls_backend;
+ req->lr_fid = fid;
+ req->lr_newfid = newfid;
+ error = be->walk(be->softc, req);
+
+ /*
+ * If newfid == fid, then fid itself has (potentially) changed,
+ * but is still valid. Otherwise set newfid valid on
+ * success, and destroy it on error.
+ */
+ if (newfid != fid) {
+ if (error == 0)
+ l9p_fid_setvalid(newfid);
+ else
+ l9p_connection_remove_fid(conn, newfid);
+ }
+
+ /*
+ * If we walked any name elements, the last (n-1'th) qid
+ * has the type (dir vs file) for the new fid. Otherwise
+ * the type of newfid is the same as fid. Of course, if
+ * n==0 and fid==newfid, fid is already set up correctly
+ * as the whole thing was a big no-op, but it's safe to
+ * copy its dir bit to itself.
+ */
+ if (error == 0) {
+ n = req->lr_resp.rwalk.nwqid;
+ if (n > 0) {
+ if (req->lr_resp.rwalk.wqid[n - 1].type & L9P_QTDIR)
+ l9p_fid_setdir(newfid);
+ } else {
+ if (l9p_fid_isdir(fid))
+ l9p_fid_setdir(newfid);
+ }
+ }
+ return (error);
+}
+
+static int
+l9p_dispatch_twrite(struct l9p_request *req)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ struct l9p_backend *be;
+ struct l9p_fid *fid;
+ int error;
+
+ /* Cannot require open due to xattr write, but can forbid dir. */
+ error = fid_lookup(conn, req->lr_req.hdr.fid, EINVAL,
+ F_FORBID_DIR, &req->lr_fid);
+ if (error)
+ return (error);
+
+ /*
+ * Adjust to point to the data to be written (a la
+ * l9p_dispatch_tread, but we're pointing into the request
+ * buffer rather than the response):
+ *
+ * size[4] + Twrite[1] + tag[2] + fid[4] + offset[8] + count[4] = 23
+ */
+ l9p_seek_iov(req->lr_req_msg.lm_iov, req->lr_req_msg.lm_niov,
+ req->lr_data_iov, &req->lr_data_niov, 23);
+
+ /*
+ * Unlike read, write and xattrwrite are optional (for R/O fs).
+ *
+ * TODO:
+ * allow async write
+ */
+ be = conn->lc_server->ls_backend;
+ fid = req->lr_fid;
+ if (l9p_fid_isxattr(fid)) {
+ error = be->xattrwrite != NULL ?
+ be->xattrwrite(be->softc, req) : ENOSYS;
+ } else if (l9p_fid_isopen(fid)) {
+ error = be->write != NULL ?
+ be->write(be->softc, req) : ENOSYS;
+ } else {
+ error = EINVAL;
+ }
+
+ return (error);
+}
+
+static int
+l9p_dispatch_twstat(struct l9p_request *req)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ struct l9p_backend *be;
+ int error;
+
+ error = fid_lookup(conn, req->lr_req.hdr.fid, EINVAL,
+ F_FORBID_XATTR, &req->lr_fid);
+ if (error)
+ return (error);
+
+ be = conn->lc_server->ls_backend;
+ error = be->wstat != NULL ? be->wstat(be->softc, req) : ENOSYS;
+ return (error);
+}
+
+static int
+l9p_dispatch_tstatfs(struct l9p_request *req)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ struct l9p_backend *be;
+ int error;
+
+ /* Should we allow statfs on auth fids? */
+ error = fid_lookup(conn, req->lr_req.hdr.fid, EINVAL, 0, &req->lr_fid);
+ if (error)
+ return (error);
+
+ be = conn->lc_server->ls_backend;
+ error = be->statfs(be->softc, req);
+ return (error);
+}
+
+static int
+l9p_dispatch_tlopen(struct l9p_request *req)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ struct l9p_backend *be;
+ int error;
+
+ error = fid_lookup(conn, req->lr_req.hdr.fid, ENOENT,
+ F_FORBID_OPEN | F_FORBID_XATTR, &req->lr_fid);
+ if (error)
+ return (error);
+
+ be = conn->lc_server->ls_backend;
+
+ /*
+ * TODO:
+ * - add async open (leaves req->lr_fid in limbo)
+ */
+ error = be->lopen != NULL ? be->lopen(be->softc, req) : ENOSYS;
+ if (error == 0)
+ l9p_fid_setopen(req->lr_fid);
+ return (error);
+}
+
+static int
+l9p_dispatch_tlcreate(struct l9p_request *req)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ struct l9p_backend *be;
+ int error;
+
+ error = fid_lookup(conn, req->lr_req.hdr.fid, ENOENT,
+ F_REQUIRE_DIR | F_FORBID_OPEN, &req->lr_fid);
+ if (error)
+ return (error);
+
+ be = conn->lc_server->ls_backend;
+
+ /*
+ * TODO:
+ * - check new file name
+ * - add async create (leaves req->lr_fid in limbo)
+ */
+ error = be->lcreate != NULL ? be->lcreate(be->softc, req) : ENOSYS;
+ if (error == 0) {
+ l9p_fid_unsetdir(req->lr_fid);
+ l9p_fid_setopen(req->lr_fid);
+ }
+ return (error);
+}
+
+static int
+l9p_dispatch_tsymlink(struct l9p_request *req)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ struct l9p_backend *be;
+ int error;
+
+ /* This doesn't affect the containing dir; maybe allow OPEN? */
+ error = fid_lookup(conn, req->lr_req.hdr.fid, ENOENT,
+ F_REQUIRE_DIR | F_FORBID_OPEN, &req->lr_fid);
+ if (error)
+ return (error);
+
+ be = conn->lc_server->ls_backend;
+
+ /*
+ * TODO:
+ * - check new file name
+ */
+ error = be->symlink != NULL ? be->symlink(be->softc, req) : ENOSYS;
+ return (error);
+}
+
+static int
+l9p_dispatch_tmknod(struct l9p_request *req)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ struct l9p_backend *be;
+ int error;
+
+ /* This doesn't affect the containing dir; maybe allow OPEN? */
+ error = fid_lookup(conn, req->lr_req.hdr.fid, ENOENT,
+ F_REQUIRE_DIR | F_FORBID_OPEN, &req->lr_fid);
+ if (error)
+ return (error);
+
+ be = conn->lc_server->ls_backend;
+
+ /*
+ * TODO:
+ * - check new file name
+ */
+ error = be->mknod != NULL ? be->mknod(be->softc, req) : ENOSYS;
+ return (error);
+}
+
+static int
+l9p_dispatch_trename(struct l9p_request *req)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ struct l9p_backend *be;
+ int error;
+
+ /* Rename directory or file (including symlink etc). */
+ error = fid_lookup(conn, req->lr_req.hdr.fid, ENOENT,
+ F_FORBID_XATTR, &req->lr_fid);
+ if (error)
+ return (error);
+
+ /* Doesn't affect new dir fid; maybe allow OPEN? */
+ error = fid_lookup(conn, req->lr_req.trename.dfid, ENOENT,
+ F_REQUIRE_DIR | F_FORBID_OPEN, &req->lr_fid2);
+ if (error)
+ return (error);
+
+ be = conn->lc_server->ls_backend;
+
+ /*
+ * TODO:
+ * - check new file name (trename.name)
+ */
+ error = be->rename != NULL ? be->rename(be->softc, req) : ENOSYS;
+ return (error);
+}
+
+static int
+l9p_dispatch_treadlink(struct l9p_request *req)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ struct l9p_backend *be;
+ int error;
+
+ /*
+ * The underlying readlink will fail unless it's a symlink,
+ * and the back end has to check, but we might as well forbid
+ * directories and open files here since it's cheap.
+ */
+ error = fid_lookup(conn, req->lr_req.hdr.fid, ENOENT,
+ F_FORBID_DIR | F_FORBID_OPEN, &req->lr_fid);
+ if (error)
+ return (error);
+
+ be = conn->lc_server->ls_backend;
+
+ error = be->readlink != NULL ? be->readlink(be->softc, req) : ENOSYS;
+ return (error);
+}
+
+static int
+l9p_dispatch_tgetattr(struct l9p_request *req)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ struct l9p_backend *be;
+ int error;
+
+ error = fid_lookup(conn, req->lr_req.hdr.fid, ENOENT,
+ F_FORBID_XATTR, &req->lr_fid);
+ if (error)
+ return (error);
+
+ be = conn->lc_server->ls_backend;
+
+ error = be->getattr != NULL ? be->getattr(be->softc, req) : ENOSYS;
+ return (error);
+}
+
+static int
+l9p_dispatch_tsetattr(struct l9p_request *req)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ struct l9p_backend *be;
+ int error;
+
+ error = fid_lookup(conn, req->lr_req.hdr.fid, ENOENT,
+ F_FORBID_XATTR, &req->lr_fid);
+ if (error)
+ return (error);
+
+ be = conn->lc_server->ls_backend;
+
+ error = be->setattr != NULL ? be->setattr(be->softc, req) : ENOSYS;
+ return (error);
+}
+
+static int
+l9p_dispatch_txattrwalk(struct l9p_request *req)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ struct l9p_backend *be;
+ struct l9p_fid *fid, *newfid;
+ int error;
+
+ /*
+ * Not sure if we care if file-or-dir is open or not.
+ * However, the fid argument should always be a file or
+ * dir and the newfid argument must be supplied, must
+ * be different, and always becomes a new xattr,
+ * so this is not very much like Twalk.
+ */
+ error = fid_lookup(conn, req->lr_req.hdr.fid, ENOENT,
+ F_FORBID_XATTR, &fid);
+ if (error)
+ return (error);
+
+ newfid = l9p_connection_alloc_fid(conn, req->lr_req.txattrwalk.newfid);
+ if (newfid == NULL)
+ return (EINVAL);
+
+ be = conn->lc_server->ls_backend;
+
+ req->lr_fid = fid;
+ req->lr_newfid = newfid;
+ error = be->xattrwalk != NULL ? be->xattrwalk(be->softc, req) : ENOSYS;
+
+ /*
+ * Success/fail is similar to Twalk, except that we need
+ * to set the xattr type bit in the new fid. It's also
+ * much simpler since newfid is always a new fid.
+ */
+ if (error == 0) {
+ l9p_fid_setvalid(newfid);
+ l9p_fid_setxattr(newfid);
+ } else {
+ l9p_connection_remove_fid(conn, newfid);
+ }
+ return (error);
+}
+
+static int
+l9p_dispatch_txattrcreate(struct l9p_request *req)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ struct l9p_backend *be;
+ struct l9p_fid *fid;
+ int error;
+
+ /*
+ * Forbid incoming open fid since it's going to become an
+ * xattr fid instead. If it turns out we need to allow
+ * it, fs code will need to handle this.
+ *
+ * Curiously, qemu 9pfs uses ENOENT for a bad txattrwalk
+ * fid, but EINVAL for txattrcreate (so we do too).
+ */
+ error = fid_lookup(conn, req->lr_req.hdr.fid, EINVAL,
+ F_FORBID_XATTR | F_FORBID_OPEN, &fid);
+ if (error)
+ return (error);
+
+ be = conn->lc_server->ls_backend;
+
+ req->lr_fid = fid;
+ error = be->xattrcreate != NULL ? be->xattrcreate(be->softc, req) :
+ ENOSYS;
+
+ /*
+ * On success, fid has changed from a regular (file or dir)
+ * fid to an xattr fid.
+ */
+ if (error == 0) {
+ l9p_fid_unsetdir(fid);
+ l9p_fid_setxattr(fid);
+ }
+ return (error);
+}
+
+static int
+l9p_dispatch_treaddir(struct l9p_request *req)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ struct l9p_backend *be;
+ int error;
+
+ error = fid_lookup(conn, req->lr_req.hdr.fid, ENOENT,
+ F_REQUIRE_DIR | F_REQUIRE_OPEN, &req->lr_fid);
+ if (error)
+ return (error);
+
+ /*
+ * Adjust so that writing messages (packing data) starts
+ * right after the count field in the response.
+ *
+ * size[4] + Rreaddir[1] + tag[2] + count[4] = 11
+ */
+ l9p_seek_iov(req->lr_resp_msg.lm_iov, req->lr_resp_msg.lm_niov,
+ req->lr_data_iov, &req->lr_data_niov, 11);
+
+ be = conn->lc_server->ls_backend;
+
+ error = be->readdir != NULL ? be->readdir(be->softc, req) : ENOSYS;
+ return (error);
+}
+
+static int
+l9p_dispatch_tfsync(struct l9p_request *req)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ struct l9p_backend *be;
+ int error;
+
+ error = fid_lookup(conn, req->lr_req.hdr.fid, ENOENT,
+ F_REQUIRE_OPEN, &req->lr_fid);
+ if (error)
+ return (error);
+
+ be = conn->lc_server->ls_backend;
+
+ error = be->fsync != NULL ? be->fsync(be->softc, req) : ENOSYS;
+ return (error);
+}
+
+static int
+l9p_dispatch_tlock(struct l9p_request *req)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ struct l9p_backend *be;
+ int error;
+
+ /* Forbid directories? */
+ error = fid_lookup(conn, req->lr_req.hdr.fid, ENOENT,
+ F_REQUIRE_OPEN, &req->lr_fid);
+ if (error)
+ return (error);
+
+ be = conn->lc_server->ls_backend;
+
+ /*
+ * TODO: multiple client handling; perhaps async locking.
+ */
+ error = be->lock != NULL ? be->lock(be->softc, req) : ENOSYS;
+ return (error);
+}
+
+static int
+l9p_dispatch_tgetlock(struct l9p_request *req)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ struct l9p_backend *be;
+ int error;
+
+ error = fid_lookup(conn, req->lr_req.hdr.fid, ENOENT,
+ F_REQUIRE_OPEN, &req->lr_fid);
+ if (error)
+ return (error);
+
+ be = conn->lc_server->ls_backend;
+
+ /*
+ * TODO: multiple client handling; perhaps async locking.
+ */
+ error = be->getlock != NULL ? be->getlock(be->softc, req) : ENOSYS;
+ return (error);
+}
+
+static int
+l9p_dispatch_tlink(struct l9p_request *req)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ struct l9p_backend *be;
+ int error;
+
+ /*
+ * Note, dfid goes into fid2 in current scheme.
+ *
+ * Allow open dir? Target dir fid is not modified...
+ */
+ error = fid_lookup(conn, req->lr_req.tlink.dfid, ENOENT,
+ F_REQUIRE_DIR | F_FORBID_OPEN, &req->lr_fid2);
+ if (error)
+ return (error);
+
+ error = fid_lookup(conn, req->lr_req.hdr.fid, ENOENT,
+ F_FORBID_DIR | F_FORBID_XATTR, &req->lr_fid);
+ if (error)
+ return (error);
+
+ be = conn->lc_server->ls_backend;
+
+ error = be->link != NULL ? be->link(be->softc, req) : ENOSYS;
+ return (error);
+}
+
+static int
+l9p_dispatch_tmkdir(struct l9p_request *req)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ struct l9p_backend *be;
+ int error;
+
+ error = fid_lookup(conn, req->lr_req.hdr.fid, ENOENT,
+ F_REQUIRE_DIR | F_FORBID_OPEN, &req->lr_fid);
+ if (error)
+ return (error);
+
+ /* Slashes embedded in the name are not allowed */
+ if (strchr(req->lr_req.tlcreate.name, '/') != NULL)
+ return (EINVAL);
+
+ be = conn->lc_server->ls_backend;
+ error = be->mkdir != NULL ? be->mkdir(be->softc, req) : ENOSYS;
+ return (error);
+}
+
+static int
+l9p_dispatch_trenameat(struct l9p_request *req)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ struct l9p_backend *be;
+ int error;
+
+ error = fid_lookup(conn, req->lr_req.hdr.fid, ENOENT,
+ F_REQUIRE_DIR | F_FORBID_OPEN, &req->lr_fid);
+ if (error)
+ return (error);
+
+ error = fid_lookup(conn, req->lr_req.trenameat.newdirfid, ENOENT,
+ F_REQUIRE_DIR | F_FORBID_OPEN, &req->lr_fid2);
+ if (error)
+ return (error);
+
+ be = conn->lc_server->ls_backend;
+
+ /* TODO: check old and new names */
+ error = be->renameat != NULL ? be->renameat(be->softc, req) : ENOSYS;
+ return (error);
+}
+
+static int
+l9p_dispatch_tunlinkat(struct l9p_request *req)
+{
+ struct l9p_connection *conn = req->lr_conn;
+ struct l9p_backend *be;
+ int error;
+
+ error = fid_lookup(conn, req->lr_req.hdr.fid, ENOENT,
+ F_REQUIRE_DIR | F_FORBID_OPEN, &req->lr_fid);
+ if (error)
+ return (error);
+
+ be = conn->lc_server->ls_backend;
+
+ /* TODO: check dir-or-file name */
+ error = be->unlinkat != NULL ? be->unlinkat(be->softc, req) : ENOSYS;
+ return (error);
+}