diff options
author | Jakub Wojciech Klama <jceel@FreeBSD.org> | 2020-04-29 16:24:32 +0000 |
---|---|---|
committer | Jakub Wojciech Klama <jceel@FreeBSD.org> | 2020-04-29 16:24:32 +0000 |
commit | 6d9daf7da2883bacd006132f8a1b6a7c388e191c (patch) | |
tree | 0a8368af46b0e674f120a4dde9a1609648f59c07 /request.c | |
download | src-6d9daf7da2883bacd006132f8a1b6a7c388e191c.tar.gz src-6d9daf7da2883bacd006132f8a1b6a7c388e191c.zip |
Notes
Diffstat (limited to 'request.c')
-rw-r--r-- | request.c | 1440 |
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); +} |