diff --git a/backend/backend.h b/backend/backend.h index 082df91..2b4bf2d 100644 --- a/backend/backend.h +++ b/backend/backend.h @@ -53,6 +53,9 @@ struct l9p_backend { int (*setattr)(void *, struct l9p_request *); int (*xattrwalk)(void *, struct l9p_request *); int (*xattrcreate)(void *, struct l9p_request *); + int (*xattrread)(void *, struct l9p_request *); + int (*xattrwrite)(void *, struct l9p_request *); + int (*xattrclunk)(void *, struct l9p_fid *); int (*readdir)(void *, struct l9p_request *); int (*fsync)(void *, struct l9p_request *); int (*lock)(void *, struct l9p_request *); diff --git a/request.c b/request.c index 0c5af44..adcb68b 100644 --- a/request.c +++ b/request.c @@ -393,76 +393,75 @@ l9p_init_msg(struct l9p_message *msg, struct l9p_request *req, sizeof (struct iovec) * req->lr_data_niov); } -/* - * Generic handler for operations that require valid fid. - * - * Decodes header fid to file, then calls backend function - * (*be)(softc, req). Returns given if the fid is invalid - * or ENOSYS if the backend function is not there. - */ -static inline int l9p_fid_dispatch(struct l9p_request *req, int err, - int (*be)(void *, struct l9p_request *)) -{ - struct l9p_connection *conn = req->lr_conn; - - req->lr_fid = ht_find(&conn->lc_files, req->lr_req.hdr.fid); - if (req->lr_fid == NULL) - return (err); - - if (be == NULL) - return (ENOSYS); - - return ((*be)(conn->lc_server->ls_backend->softc, req)); -} - -/* - * Generic handler for operations that need two fid's. - * Note that the 2nd fid must be supplied by the caller; the - * corresponding openfile goes into req->lr_f2. - */ -static inline int l9p_2fid_dispatch(struct l9p_request *req, uint32_t fid2, - int err, int (*be)(void *, struct l9p_request *)) -{ - struct l9p_connection *conn = req->lr_conn; - - req->lr_fid = ht_find(&conn->lc_files, req->lr_req.hdr.fid); - if (req->lr_fid == NULL) - return (err); - - req->lr_fid2 = ht_find(&conn->lc_files, fid2); - if (req->lr_fid2 == NULL) - return (err); - - if (be == NULL) - return (ENOSYS); - - return ((*be)(conn->lc_server->ls_backend->softc, req)); -} +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, /* requre 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 */ +}; /* - * Generic handler for read-like operations (read and readdir). + * 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"). * - * Backend function must exist. + * 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 l9p_read_dispatch(struct l9p_request *req, - int (*be)(void *, struct l9p_request *)) +static inline int +fid_lookup(struct l9p_connection *conn, uint32_t fid, int err, int flags, + struct l9p_fid **afile) { - struct l9p_connection *conn = req->lr_conn; + struct l9p_fid *file; - req->lr_fid = ht_find(&conn->lc_files, req->lr_req.hdr.fid); - if (!req->lr_fid) - return (EINVAL); + file = ht_find(&conn->lc_files, fid); + if (file == NULL) + return (err); /* - * Adjust so that writing messages (packing data) starts - * right after the count field in the response. + * 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). * - * size[4] + Rread(dir)[1] + tag[2] + count[4] = 11 + * For now, this serves for bug-detecting. */ - l9p_seek_iov(req->lr_resp_msg.lm_iov, req->lr_resp_msg.lm_niov, - req->lr_data_iov, &req->lr_data_niov, 11); + assert(l9p_fid_isvalid(file)); - return ((*be)(conn->lc_server->ls_backend->softc, req)); + /* + * 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); } /* @@ -539,6 +538,21 @@ l9p_dispatch_tattach(struct l9p_request *req) 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); @@ -546,15 +560,21 @@ l9p_dispatch_tattach(struct l9p_request *req) 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. */ - if (error == 0) + /* + * On success, fid becomes valid; on failure, disconnect. + * It certainly *should* be a directory here... + */ + if (error == 0) { l9p_fid_setvalid(fid); - else + if (req->lr_resp.rattach.qid.type & L9P_QTDIR) + l9p_fid_setdir(fid); + } else l9p_connection_remove_fid(conn, fid); return (error); } @@ -567,14 +587,29 @@ l9p_dispatch_tclunk(struct l9p_request *req) struct l9p_fid *fid; int error; - fid = ht_find(&conn->lc_files, req->lr_req.hdr.fid); - if (fid == NULL) - return (ENOENT); + /* 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); - error = be->clunk(be->softc, 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); @@ -609,22 +644,105 @@ l9p_dispatch_tflush(struct l9p_request *req __unused) 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 (l9p_fid_dispatch(req, EINVAL, req->lr_conn->lc_server->ls_backend->create)); + 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; - return (l9p_fid_dispatch(req, EINVAL, req->lr_conn->lc_server->ls_backend->open)); + /* + * 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 (l9p_read_dispatch(req, req->lr_conn->lc_server->ls_backend->read)); + return (error); } static int @@ -635,9 +753,13 @@ l9p_dispatch_tremove(struct l9p_request *req) struct l9p_fid *fid; int error; - fid = ht_find(&conn->lc_files, req->lr_req.hdr.fid); - if (fid == NULL) - return (EINVAL); + /* + * ?? 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); @@ -651,8 +773,33 @@ l9p_dispatch_tremove(struct l9p_request *req) 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 (l9p_fid_dispatch(req, ENOENT, req->lr_conn->lc_server->ls_backend->stat)); + return (error); } static int @@ -661,11 +808,14 @@ 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; - fid = ht_find(&conn->lc_files, req->lr_req.hdr.fid); - if (fid == NULL) - return (ENOENT); + /* 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, @@ -679,8 +829,9 @@ l9p_dispatch_twalk(struct l9p_request *req) req->lr_fid = fid; req->lr_newfid = newfid; error = be->walk(be->softc, req); + /* - * If newfid == fid, then fid itself has changed, + * If newfid == fid, then fid itself has (potentially) changed, * but is still valid. Otherwise set newfid valid on * success, and destroy it on error. */ @@ -690,6 +841,25 @@ l9p_dispatch_twalk(struct l9p_request *req) 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); } @@ -697,96 +867,264 @@ 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; - req->lr_fid = ht_find(&conn->lc_files, req->lr_req.twalk.hdr.fid); - if (req->lr_fid == NULL) - return (EINVAL); - - if (!conn->lc_server->ls_backend->write) - return (ENOSYS); + /* 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_read_dispatch, but we're pointing into the request + * 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 + * 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); - return (conn->lc_server->ls_backend->write(conn->lc_server->ls_backend->softc, req)); + /* + * 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); - return (l9p_fid_dispatch(req, EINVAL, req->lr_conn->lc_server->ls_backend->wstat)); + 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; - return (l9p_fid_dispatch(req, EINVAL, req->lr_conn->lc_server->ls_backend->statfs)); + /* 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); - return (l9p_fid_dispatch(req, ENOENT, req->lr_conn->lc_server->ls_backend->lopen)); + 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); - return (l9p_fid_dispatch(req, ENOENT, req->lr_conn->lc_server->ls_backend->lcreate)); + 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; - return (l9p_fid_dispatch(req, ENOENT, req->lr_conn->lc_server->ls_backend->symlink)); + /* + * 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); - return (l9p_fid_dispatch(req, ENOENT, req->lr_conn->lc_server->ls_backend->mknod)); + 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; - return (l9p_2fid_dispatch(req, req->lr_req.trename.dfid, ENOENT, - req->lr_conn->lc_server->ls_backend->rename)); + /* 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; - return (l9p_fid_dispatch(req, ENOENT, req->lr_conn->lc_server->ls_backend->readlink)); + /* + * 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); - return (l9p_fid_dispatch(req, ENOENT, req->lr_conn->lc_server->ls_backend->getattr)); + 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); - return (l9p_fid_dispatch(req, ENOENT, req->lr_conn->lc_server->ls_backend->setattr)); + be = conn->lc_server->ls_backend; + + error = be->setattr != NULL ? be->setattr(be->softc, req) : ENOSYS; + return (error); } static int @@ -797,21 +1135,27 @@ l9p_dispatch_txattrwalk(struct l9p_request *req) struct l9p_fid *fid, *newfid; int error; - fid = ht_find(&conn->lc_files, req->lr_req.hdr.fid); - if (fid == NULL) - return (ENOENT); + /* + * 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; - if (be->xattrwalk == NULL) - return (ENOSYS); req->lr_fid = fid; req->lr_newfid = newfid; - error = be->xattrwalk(be->softc, req); + error = be->xattrwalk != NULL ? be->xattrwalk(be->softc, req) : ENOSYS; /* * Success/fail is similar to Twalk, except that we need @@ -836,81 +1180,217 @@ l9p_dispatch_txattrcreate(struct l9p_request *req) 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). */ - fid = ht_find(&conn->lc_files, req->lr_req.hdr.fid); - if (fid == NULL) - return (EINVAL); + 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; - if (be->xattrcreate == NULL) - return (ENOSYS); req->lr_fid = fid; - error = be->xattrcreate(be->softc, req); + error = be->xattrcreate != NULL ? be->xattrcreate(be->softc, req) : + ENOSYS; + /* - * On success, fid has changed from regular fid to xattr fid. + * On success, fid has changed from a regular (file or dir) + * fid to an xattr fid. */ - if (error == 0) + 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; - return (l9p_read_dispatch(req, req->lr_conn->lc_server->ls_backend->readdir)); + 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; - return (l9p_fid_dispatch(req, ENOENT, req->lr_conn->lc_server->ls_backend->fsync)); + /* + * Forbid dir? Not clear what it means to fsync them. + * Current backend code assumes open file (not dir), + * so we'll forbid directories here for now. + */ + error = fid_lookup(conn, req->lr_req.hdr.fid, ENOENT, + F_REQUIRE_OPEN | F_FORBID_DIR, &req->lr_fid); + if (error) + return (error); + + be = conn->lc_server->ls_backend; + + /* + * TODO: async fsync, maybe. + */ + 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); - return (l9p_fid_dispatch(req, ENOENT, req->lr_conn->lc_server->ls_backend->lock)); + 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; - return (l9p_fid_dispatch(req, ENOENT, req->lr_conn->lc_server->ls_backend->getlock)); + 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; - return (l9p_2fid_dispatch(req, req->lr_req.tlink.dfid, ENOENT, - req->lr_conn->lc_server->ls_backend->link)); + 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; - return (l9p_fid_dispatch(req, ENOENT, req->lr_conn->lc_server->ls_backend->mkdir)); + 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 directory name */ + 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); - return (l9p_2fid_dispatch(req, req->lr_req.trenameat.newdirfid, ENOENT, - req->lr_conn->lc_server->ls_backend->renameat)); + 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; - return (l9p_fid_dispatch(req, ENOENT, req->lr_conn->lc_server->ls_backend->unlinkat)); + 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); }