diff options
Diffstat (limited to 'lib9p/srv.c')
-rw-r--r-- | lib9p/srv.c | 1679 |
1 files changed, 982 insertions, 697 deletions
diff --git a/lib9p/srv.c b/lib9p/srv.c index 60a1bb0..08ccfc3 100644 --- a/lib9p/srv.c +++ b/lib9p/srv.c @@ -4,7 +4,6 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -#include <alloca.h> #include <inttypes.h> /* for PRI* */ #include <limits.h> /* for SSIZE_MAX, not set by newlib */ #include <stddef.h> /* for size_t */ @@ -17,8 +16,10 @@ #include <libcr/coroutine.h> #include <libcr_ipc/chan.h> #include <libcr_ipc/mutex.h> +#include <libmisc/alloc.h> #include <libmisc/assert.h> #include <libmisc/endian.h> +#include <libmisc/map.h> #include <libhw/generic/net.h> #define LOG_NAME 9P_SRV @@ -31,16 +32,6 @@ #include "config.h" -#ifndef CONFIG_9P_SRV_MAX_FIDS - #error config.h must define CONFIG_9P_SRV_MAX_FIDS -#endif -#ifndef CONFIG_9P_SRV_MAX_REQS - #error config.h must define CONFIG_9P_SRV_MAX_REQS -#endif -#ifndef CONFIG_9P_SRV_MAX_DEPTH - /* 1=just the root dir, 2=just files in the root dir, 3=1 subdir, ... */ - #error config.h must define CONFIG_9P_SRV_MAX_DEPTH -#endif #ifndef CONFIG_9P_SRV_MAX_MSG_SIZE #error config.h must define CONFIG_9P_SRV_MAX_MSG_SIZE #endif @@ -54,23 +45,39 @@ static_assert(CONFIG_9P_SRV_MAX_HOSTMSG_SIZE <= SSIZE_MAX); bool lib9p_srv_flush_requested(struct lib9p_srv_ctx *ctx) { assert(ctx); - return _lib9p_srv_flushch_can_send(&ctx->_flushch); + return cr_chan_can_send(&ctx->flush_ch); } -int lib9p_srv_acknowledge_flush(struct lib9p_srv_ctx *ctx) { +void lib9p_srv_acknowledge_flush(struct lib9p_srv_ctx *ctx) { assert(ctx); - assert(_lib9p_srv_flushch_can_send(&ctx->_flushch)); - lib9p_error(&ctx->basectx, LINUX_ECANCELED, "request canceled by flush"); - _lib9p_srv_flushch_send(&ctx->_flushch, true); - return -1; + assert(cr_chan_can_send(&ctx->flush_ch)); + ctx->flush_acknowledged = true; } +#define req_debugf(fmt, ...) \ + debugf("cid=%zu: %s(tag=%"PRIu16"): " fmt, \ + cr_getcid(), \ + lib9p_msgtype_str(ctx->basectx.version, ctx->net_bytes[4]), \ + ctx->tag \ + __VA_OPT__(,) __VA_ARGS__) + /* structs ********************************************************************/ +enum srv_filetype { + SRV_FILETYPE_FILE, + SRV_FILETYPE_DIR, + SRV_FILETYPE_AUTH, +}; + +/* path *****************************************/ + typedef typeof( ((struct lib9p_qid){}).path ) srv_path_t; struct srv_pathinfo { lo_interface lib9p_srv_file file; + enum srv_filetype type; + /* .parent_dir is used for (1) Twalk(".."), and (2) for checking + * permissions on the parent directory for remove(). */ srv_path_t parent_dir; /* References from other srv_pathinfos (via .parent_dir) or @@ -80,21 +87,19 @@ struct srv_pathinfo { unsigned int io_refcount; }; -#define NAME pathmap -#define KEY_T srv_path_t -#define VAL_T struct srv_pathinfo -/* ( naive ) + ( working space for walk() ) */ -#define CAP ( (CONFIG_9P_SRV_MAX_FIDS*CONFIG_9P_SRV_MAX_DEPTH) + (CONFIG_9P_SRV_MAX_REQS*2) ) -#include "map.h" +/* fid ******************************************/ #define FIDFLAG_OPEN_R (1<<0) #define FIDFLAG_OPEN_W (1<<1) #define FIDFLAG_RCLOSE (1<<2) +#define FIDFLAG_APPEND (1<<3) #define FIDFLAG_OPEN (FIDFLAG_OPEN_R|FIDFLAG_OPEN_W) -struct _srv_fidinfo { +struct srv_fidinfo { srv_path_t path; + struct lib9p_srv_userid *user; uint8_t flags; + enum srv_filetype type; union { struct { lo_interface lib9p_srv_fio io; @@ -103,31 +108,26 @@ struct _srv_fidinfo { lo_interface lib9p_srv_dio io; size_t idx; uint64_t off; + struct lib9p_srv_dirent buffered_dirent; } dir; + struct { + struct lib9p_s aname; + bool completed; + } auth; }; }; -#define NAME fidmap -#define KEY_T lib9p_fid_t -#define VAL_T struct _srv_fidinfo -#define CAP CONFIG_9P_SRV_MAX_FIDS -#include "map.h" - -#define NAME reqmap -#define KEY_T lib9p_tag_t -#define VAL_T struct _lib9p_srv_req * -#define CAP CONFIG_9P_SRV_MAX_REQS -#include "map.h" - -/* The hierarchy of concepts is: +/* contexts ************************************** + * + * The hierarchy of contexts is: * * server -> connection -> session -> request * */ -/* struct _srv_srv {} is defined in <lib9p/srv.h> */ +/* struct lib9p_srv {} is defined in <lib9p/srv.h> */ -struct _srv_conn { +struct srv_conn { /* immutable */ struct lib9p_srv *parent_srv; lo_interface net_stream_conn fd; @@ -136,34 +136,236 @@ struct _srv_conn { cr_mutex_t writelock; }; -struct _srv_sess { +#define srv_sess _lib9p_srv_sess +MAP_DECLARE(srv_pathmap, srv_path_t, struct srv_pathinfo); +MAP_DECLARE(srv_fidmap, lib9p_fid_t, struct srv_fidinfo); +MAP_DECLARE(srv_reqmap, lib9p_tag_t, struct lib9p_srv_ctx *); +struct srv_sess { /* immutable */ - struct _srv_conn *parent_conn; + struct srv_conn *parent_conn; enum lib9p_version version; uint32_t max_msg_size; - uint32_t rerror_overhead; /* mutable */ bool initialized; bool closing; - struct pathmap paths; /* srv_path_t => lib9p_srv_file + metadata */ - struct fidmap fids; /* lib9p_fid_t => lib9p_srv_{fio,dio} + metadata */ - struct reqmap reqs; /* lib9p_tag_t => *_lib9p_srv_req */ + struct srv_pathmap paths; /* srv_path_t => `lib9p_srv_file` + metadata */ + struct srv_fidmap fids; /* lib9p_fid_t => `lib9p_srv_{fio,dio}` + metadata */ + struct srv_reqmap reqs; /* lib9p_tag_t => `struct srv_req *` */ }; -struct _lib9p_srv_req { - /* immutable */ - struct _srv_sess *parent_sess; - uint16_t tag; - uint8_t *net_bytes; - /* mutable */ - struct lib9p_srv_ctx ctx; -}; +#define srv_req lib9p_srv_ctx /* struct lib9p_srv_ctx {} is defined in <lib9p/srv.h> */ + +/* utilities for the above types **********************************************/ + +static inline enum srv_filetype srv_qid_filetype(struct lib9p_qid qid) { + if (qid.type & LIB9P_QT_AUTH) + return SRV_FILETYPE_AUTH; + if (qid.type & LIB9P_QT_DIR) + return SRV_FILETYPE_DIR; + return SRV_FILETYPE_FILE; +} + +static inline bool srv_check_perm(struct srv_req *ctx, struct lib9p_srv_stat *stat, uint8_t action) { + assert(ctx); + assert(stat); + assert(action); + + /* TODO actually check user and group instead of just assuming "other". */ + uint8_t mode = (uint8_t)(stat->mode & 07); + + return mode & action; +} + +[[gnu::unused]] +static struct lib9p_srv_userid *srv_userid_new(struct lib9p_s name +#if CONFIG_9P_ENABLE_9P2000_u || CONFIG_9P_ENABLE_9P2000_L + , lib9p_nuid_t num +#endif + ) { + struct lib9p_srv_userid *ret = malloc(sizeof(struct lib9p_srv_userid) + name.len); + if (!ret) + return NULL; +#if CONFIG_9P_ENABLE_9P2000_u || CONFIG_9P_ENABLE_9P2000_L + ret->num = num; +#endif + ret->name.len = name.len; + ret->name.utf8 = (void *)&ret[1]; + memcpy(ret->name.utf8, name.utf8, name.len); + ret->refcount = 1; + return ret; +} +#if !(CONFIG_9P_ENABLE_9P2000_u || CONFIG_9P_ENABLE_9P2000_L) +#define srv_userid_new(name, num) srv_userid_new(name) +#endif + +static struct lib9p_srv_userid *srv_userid_decref(struct lib9p_srv_userid *userid) { + assert(userid); + assert(userid->refcount); + userid->refcount--; + if (!userid->refcount) + free(userid); + return NULL; +} + +static struct lib9p_srv_userid *srv_userid_incref(struct lib9p_srv_userid *userid) { + assert(userid); + userid->refcount++; + return userid; +} + +/** + * Ensures that `file` is saved into the pathmap, and increments the + * gc_refcount by 1 (for presumptive insertion into the fidmap). + * parent_path's gc_refcount is also incremented as appropriate. + * + * Returns a pointer to the stored pathinfo. + */ +static inline struct srv_pathinfo *srv_path_save(struct srv_req *ctx, + lo_interface lib9p_srv_file file, + srv_path_t parent_path) { + assert(ctx); + assert(!LO_IS_NULL(file)); + + struct lib9p_qid qid = LO_CALL(file, qid); + struct srv_pathinfo *pathinfo = map_load(&ctx->parent_sess->paths, qid.path); + if (pathinfo) + assert(LO_EQ(pathinfo->file, file)); + else { + pathinfo = map_store(&ctx->parent_sess->paths, qid.path, + (struct srv_pathinfo){ + .file = file, + .type = srv_qid_filetype(qid), + .parent_dir = parent_path, + .gc_refcount = 0, + .io_refcount = 0, + }); + assert(pathinfo); + if (parent_path != qid.path) { + struct srv_pathinfo *parent = map_load(&ctx->parent_sess->paths, parent_path); + assert(parent); + parent->gc_refcount++; + } + } + pathinfo->gc_refcount++; + return pathinfo; +} + +/** + * Decrement the path's gc_refcount, and trigger garbage collection as + * appropriate. + */ +static inline void srv_path_decref(struct srv_req *ctx, srv_path_t path) { + assert(ctx); + + for (;;) { + struct srv_pathinfo *pathinfo = map_load(&ctx->parent_sess->paths, path); + assert(pathinfo); + pathinfo->gc_refcount--; + if (pathinfo->gc_refcount) + break; + srv_path_t parent_path = pathinfo->parent_dir; + LO_CALL(pathinfo->file, free); + map_del(&ctx->parent_sess->paths, path); + if (parent_path == path) + break; + path = parent_path; + } +} + +static inline void srv_fid_del(struct srv_req *ctx, lib9p_fid_t fid, struct srv_fidinfo *fidinfo, bool remove) { + assert(ctx); + assert(!ctx->user); + assert(fidinfo); + + if (fidinfo->flags & FIDFLAG_RCLOSE) + remove = true; + ctx->user = srv_userid_incref(fidinfo->user); + + struct srv_pathinfo *pathinfo = map_load(&ctx->parent_sess->paths, fidinfo->path); + assert(pathinfo); + + if (remove) + LO_CALL(pathinfo->file, remove, ctx); + + if (fidinfo->flags & FIDFLAG_OPEN) { + switch (fidinfo->type) { + case SRV_FILETYPE_DIR: + LO_CALL(fidinfo->dir.io, iofree); + break; + case SRV_FILETYPE_FILE: + LO_CALL(fidinfo->file.io, iofree); + break; + case SRV_FILETYPE_AUTH: + assert_notreached("TODO: auth not yet implemented"); + break; + } + pathinfo->io_refcount--; + } + fidinfo->user = srv_userid_decref(fidinfo->user); + srv_path_decref(ctx, fidinfo->path); + map_del(&ctx->parent_sess->fids, fid); + + ctx->user = srv_userid_decref(ctx->user); +} + +/** + * Store fid as pointing to pathinfo. Assumes that + * pathinfo->gc_refcount has already been incremented; does *not* + * decrement it on failure. + */ +static inline struct srv_fidinfo *srv_fid_store(struct srv_req *ctx, lib9p_fid_t fid, struct srv_pathinfo *pathinfo, bool overwrite) { + assert(ctx); + assert(fid != LIB9P_FID_NOFID); + assert(pathinfo); + + struct lib9p_qid qid = LO_CALL(pathinfo->file, qid); + + struct srv_fidinfo *old_fidinfo = map_load(&ctx->parent_sess->fids, fid); + if (old_fidinfo) { + if (overwrite) { + /* This should only happen from Twalk; because + * directories cannot be RCLOSE and Twalk cannot walk on + * FIDs open for I/O, we can skip most of + * srv_fid_del(). */ + assert(old_fidinfo->type == SRV_FILETYPE_DIR); + assert(old_fidinfo->flags == 0); + + old_fidinfo->user = srv_userid_decref(old_fidinfo->user); + srv_path_decref(ctx, old_fidinfo->path); + map_del(&ctx->parent_sess->fids, fid); + } else { + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EBADF, "FID already in use"); + return NULL; + } + } + struct srv_fidinfo *fidinfo = map_store(&ctx->parent_sess->fids, fid, (struct srv_fidinfo){ + .path = qid.path, + .type = srv_qid_filetype(qid), + .user = srv_userid_incref(ctx->user), + }); + assert(fidinfo); + return fidinfo; +} /* base utilities *************************************************************/ -#define nonrespond_errorf errorf +static void srv_msglog(struct srv_req *req, enum lib9p_msg_type typ, void *hostmsg) { + struct lib9p_srv *srv = req->parent_sess->parent_conn->parent_srv; + if (srv->msglog) { + srv->msglog(req, typ, hostmsg); + return; + } + /* It sucks that %v trips -Wformat and -Wformat-extra-args + * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47781 */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat" +#pragma GCC diagnostic ignored "-Wformat-extra-args" + infof("%c %v", typ % 2 ? '<' : '>', lo_box_lib9p_msg_as_fmt_formatter(&req->basectx, typ, hostmsg)); +#pragma GCC diagnostic pop +} -static ssize_t write_Rmsg(struct _lib9p_srv_req *req, struct lib9p_Rmsg_send_buf *resp) { +static ssize_t srv_write_Rmsg(struct srv_req *req, struct lib9p_Rmsg_send_buf *resp) { ssize_t r; cr_mutex_lock(&req->parent_sess->parent_conn->writelock); r = io_writev(req->parent_sess->parent_conn->fd, resp->iov, resp->iov_cnt); @@ -171,59 +373,62 @@ static ssize_t write_Rmsg(struct _lib9p_srv_req *req, struct lib9p_Rmsg_send_buf return r; } -static void respond_error(struct _lib9p_srv_req *req) { -#if CONFIG_9P_ENABLE_9P2000_u - assert(req->ctx.basectx.err_num); +#define srv_nonrespond_errorf errorf + +static void srv_respond_error(struct srv_req *req) { +#if CONFIG_9P_ENABLE_9P2000_u || CONFIG_9P_ENABLE_9P2000_L + assert(req->basectx.err_num); #endif - assert(req->ctx.basectx.err_msg[0]); + assert(req->basectx.err_msg[0]); ssize_t r; struct lib9p_msg_Rerror host = { .tag = req->tag, - .ename = lib9p_strn(req->ctx.basectx.err_msg, - CONFIG_9P_MAX_ERR_SIZE), + .errstr = lib9p_strn(req->basectx.err_msg, + CONFIG_9P_MAX_ERR_SIZE), #if CONFIG_9P_ENABLE_9P2000_u - .errno = req->ctx.basectx.err_num, + .errnum = req->basectx.err_num, #endif }; - struct _srv_sess *sess = req->parent_sess; + struct srv_sess *sess = req->parent_sess; + + /* XXX: This assumes that a version's min_msg_size is the + * Rerror overhead. That's true for the current + * implementation of core_gen, but is a sneaky assumption. */ + uint32_t overhead = lib9p_version_min_msg_size(sess->version); /* Truncate the error-string if necessary to avoid needing to - * return LINUX_ERANGE. */ - if (((uint32_t)host.ename.len) + sess->rerror_overhead > sess->max_msg_size) - host.ename.len = sess->max_msg_size - sess->rerror_overhead; + * return LIB9P_ERRNO_L_ERANGE. */ + if (((uint32_t)host.errstr.len) + overhead > sess->max_msg_size) + host.errstr.len = sess->max_msg_size - overhead; struct lib9p_Rmsg_send_buf net; - lib9p_Rmsg_marshal(&req->ctx.basectx, + lib9p_Rmsg_marshal(&req->basectx, LIB9P_TYP_Rerror, &host, &net); -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wformat" -#pragma GCC diagnostic ignored "-Wformat-extra-args" - infof("< %v", lo_box_lib9p_msg_Rerror_as_fmt_formatter(&host)); -#pragma GCC diagnostic pop - r = write_Rmsg(req, &net); + srv_msglog(req, LIB9P_TYP_Rerror, &host); + r = srv_write_Rmsg(req, &net); if (r < 0) - nonrespond_errorf("write: %s", net_strerror(-r)); + srv_nonrespond_errorf("write: %s", net_strerror(-r)); } /* read coroutine *************************************************************/ -static bool read_exactly(lo_interface net_stream_conn fd, uint8_t *buf, size_t goal, size_t *done) { +static inline bool srv_read_exactly(lo_interface net_stream_conn fd, uint8_t *buf, size_t goal, size_t *done) { assert(buf); assert(goal); assert(done); while (*done < goal) { ssize_t r = io_read(fd, &buf[*done], goal - *done); if (r < 0) { - nonrespond_errorf("read: %s", net_strerror(-r)); + srv_nonrespond_errorf("read: %s", net_strerror(-r)); return true; } else if (r == 0) { if (*done != 0) - nonrespond_errorf("read: unexpected EOF"); + srv_nonrespond_errorf("read: unexpected EOF"); return true; } *done += r; @@ -231,151 +436,163 @@ static bool read_exactly(lo_interface net_stream_conn fd, uint8_t *buf, size_t g return false; } -static void handle_message(struct _lib9p_srv_req *ctx); - -[[noreturn]] void lib9p_srv_read_cr(struct lib9p_srv *srv, lo_interface net_stream_listener listener) { +void lib9p_srv_accept_and_read_loop(struct lib9p_srv *srv, lo_interface net_stream_listener listener) { assert(srv); assert(srv->rootdir); assert(!LO_IS_NULL(listener)); srv->readers++; - uint32_t initial_rerror_overhead = lib9p_version_min_msg_size(LIB9P_VER_unknown); - for (;;) { - struct _srv_conn conn = { - .parent_srv = srv, - .fd = LO_CALL(listener, accept), - .reader = cr_getcid(), - }; - if (LO_IS_NULL(conn.fd)) { - nonrespond_errorf("accept: error"); + lo_interface net_stream_conn conn = LO_CALL(listener, accept); + if (LO_IS_NULL(conn)) { + srv_nonrespond_errorf("accept: error"); srv->readers--; if (srv->readers == 0) while (srv->writers > 0) - _lib9p_srv_reqch_send_req(&srv->_reqch, NULL); - cr_exit(); + cr_rpc_send_req(&srv->_reqch, NULL); + return; } + lib9p_srv_read(srv, conn); + } +} - struct _srv_sess sess = { - .parent_conn = &conn, - .version = LIB9P_VER_unknown, - .max_msg_size = CONFIG_9P_SRV_MAX_MSG_SIZE, - .rerror_overhead = initial_rerror_overhead, - .initialized = false, - }; - for (;;) { - nextmsg: - /* Read the message. */ - size_t done = 0; - uint8_t buf[7]; - if (read_exactly(conn.fd, buf, 4, &done)) - goto close; - size_t goal = uint32le_decode(buf); - if (goal < 7) { - nonrespond_errorf("T-message is impossibly small"); - goto close; - } - if (read_exactly(conn.fd, buf, 7, &done)) - goto close; - struct _lib9p_srv_req req = { - .parent_sess = &sess, - .tag = uint16le_decode(&buf[5]), - .net_bytes = buf, - .ctx = { - .basectx = { - .version = sess.version, - .max_msg_size = sess.max_msg_size, - }, - }, - }; - if (goal > sess.max_msg_size) { - lib9p_errorf(&req.ctx.basectx, - LINUX_EMSGSIZE, "T-message larger than %s limit (%zu > %"PRIu32")", - sess.initialized ? "negotiated" : "server", - goal, - sess.max_msg_size); - respond_error(&req); - goto nextmsg; - } - req.net_bytes = malloc(goal); - assert(req.net_bytes); - memcpy(req.net_bytes, buf, done); - if (read_exactly(conn.fd, req.net_bytes, goal, &done)) { - free(req.net_bytes); - goto close; - } +void lib9p_srv_read(struct lib9p_srv *srv, lo_interface net_stream_conn _conn) { + assert(srv); + assert(srv->rootdir); + assert(!LO_IS_NULL(_conn)); - /* Handle the message... */ - if (req.net_bytes[4] == LIB9P_TYP_Tversion) - /* ...in this coroutine for Tversion, */ - handle_message(&req); - else - /* ...but usually in another coroutine. */ - _lib9p_srv_reqch_send_req(&srv->_reqch, &req); + struct srv_conn conn = { + .parent_srv = srv, + .fd = _conn, + .reader = cr_getcid(), + }; + struct srv_sess sess = { + .parent_conn = &conn, + .version = LIB9P_VER_unknown, + .max_msg_size = CONFIG_9P_SRV_MAX_MSG_SIZE, + .initialized = false, + }; + for (;;) { + /* Read the message. */ + size_t done = 0; + uint8_t buf[7]; + if (srv_read_exactly(conn.fd, buf, 4, &done)) + break; + size_t goal = uint32le_decode(buf); + if (goal < 7) { + srv_nonrespond_errorf("T-message is impossibly small"); + break; + } + if (srv_read_exactly(conn.fd, buf, 7, &done)) + break; + struct srv_req req = { + .basectx = { + .version = sess.version, + .max_msg_size = sess.max_msg_size, + }, + + .parent_sess = &sess, + .tag = uint16le_decode(&buf[5]), + .net_bytes = buf, + }; + if (goal > sess.max_msg_size) { + lib9p_errorf(&req.basectx, + LIB9P_ERRNO_L_EMSGSIZE, "T-message larger than %s limit (%zu > %"PRIu32")", + sess.initialized ? "negotiated" : "server", + goal, + sess.max_msg_size); + srv_respond_error(&req); + continue; } - close: - if (sess.reqs.len == 0) - io_close(conn.fd); - else { - io_close_read(conn.fd); - sess.closing = true; - cr_pause_and_yield(); - assert(sess.reqs.len == 0); - io_close_write(conn.fd); + req.net_bytes = malloc(goal); + assert(req.net_bytes); + memcpy(req.net_bytes, buf, done); + if (srv_read_exactly(conn.fd, req.net_bytes, goal, &done)) { + free(req.net_bytes); + break; } + + /* Handle the message... */ + if (req.net_bytes[4] == LIB9P_TYP_Tversion) + /* ...in this coroutine for Tversion, */ + lib9p_srv_worker(&req); + else + /* ...but usually in another coroutine. */ + cr_rpc_send_req(&srv->_reqch, &req); } + if (map_len(&sess.reqs) == 0) + io_close(conn.fd); + else { + io_close_read(conn.fd); + sess.closing = true; + cr_pause_and_yield(); + assert(map_len(&sess.reqs) == 0); + io_close_write(conn.fd); + } + + assert(map_len(&sess.reqs) == 0); + map_free(&sess.reqs); + + struct srv_req pseudoreq = { + .basectx = { + .version = sess.version, + .max_msg_size = sess.max_msg_size, + }, + .parent_sess = &sess, + }; + MAP_FOREACH(&sess.fids, fid, fidinfo) { + srv_fid_del(&pseudoreq, fid, fidinfo, false); + if (lib9p_ctx_has_error(&pseudoreq.basectx)) { + srv_nonrespond_errorf("clunk: %.*s", CONFIG_9P_MAX_ERR_SIZE, pseudoreq.basectx.err_msg); + lib9p_ctx_clear_error(&pseudoreq.basectx); + } + } + map_free(&sess.fids); + + assert(map_len(&sess.paths) == 0); + map_free(&sess.paths); } /* write coroutine ************************************************************/ -COROUTINE lib9p_srv_write_cr(void *_srv) { - struct _lib9p_srv_req req; +void lib9p_srv_worker_loop(struct lib9p_srv *srv) { + struct srv_req req; _lib9p_srv_reqch_req_t rpc_handle; - struct lib9p_srv *srv = _srv; assert(srv); assert(srv->rootdir); - cr_begin(); srv->writers++; for (;;) { /* Receive the request from the reader coroutine. ************/ - rpc_handle = _lib9p_srv_reqch_recv_req(&srv->_reqch); + rpc_handle = cr_rpc_recv_req(&srv->_reqch); if (!rpc_handle.req) { srv->writers--; - _lib9p_srv_reqch_send_resp(rpc_handle, 0); - cr_exit(); + cr_rpc_send_resp(rpc_handle, 0); + return; } /* Copy the request from the reader coroutine's * stack to our stack. */ req = *rpc_handle.req; /* Record that we have it. */ - reqmap_store(&req.parent_sess->reqs, req.tag, &req); + struct srv_req **reqpp = map_store(&req.parent_sess->reqs, req.tag, &req); + assert(reqpp && *reqpp == &req); /* Notify the reader coroutine that we're done with * its data. */ - _lib9p_srv_reqch_send_resp(rpc_handle, 0); + cr_rpc_send_resp(rpc_handle, 0); /* Process the request. **************************************/ - handle_message(&req); - - /* Release resources. ****************************************/ - while (_lib9p_srv_flushch_can_send(&req.ctx._flushch)) - _lib9p_srv_flushch_send(&req.ctx._flushch, false); - reqmap_del(&req.parent_sess->reqs, req.tag); - if (req.parent_sess->closing && !req.parent_sess->reqs.len) - cr_unpause(req.parent_sess->parent_conn->reader); + lib9p_srv_worker(&req); } - - cr_end(); } #define _HANDLER_PROTO(typ) \ - static void handle_T##typ(struct _lib9p_srv_req *, \ - struct lib9p_msg_T##typ *, \ - struct lib9p_msg_R##typ *) + static void handle_T##typ(struct srv_req *, \ + struct lib9p_msg_T##typ *) _HANDLER_PROTO(version); +#if _LIB9P_ENABLE_stat _HANDLER_PROTO(auth); _HANDLER_PROTO(attach); _HANDLER_PROTO(flush); @@ -388,201 +605,120 @@ _HANDLER_PROTO(clunk); _HANDLER_PROTO(remove); _HANDLER_PROTO(stat); _HANDLER_PROTO(wstat); +#endif +#if CONFIG_9P_ENABLE_9P2000_p9p +_HANDLER_PROTO(openfd); +#endif #if CONFIG_9P_ENABLE_9P2000_e _HANDLER_PROTO(session); _HANDLER_PROTO(sread); _HANDLER_PROTO(swrite); #endif -typedef void (*tmessage_handler)(struct _lib9p_srv_req *, void *, void *); - -static tmessage_handler tmessage_handlers[0x100] = { - [LIB9P_TYP_Tversion] = (tmessage_handler)handle_Tversion, - [LIB9P_TYP_Tauth] = (tmessage_handler)handle_Tauth, - [LIB9P_TYP_Tattach] = (tmessage_handler)handle_Tattach, - [LIB9P_TYP_Tflush] = (tmessage_handler)handle_Tflush, - [LIB9P_TYP_Twalk] = (tmessage_handler)handle_Twalk, - [LIB9P_TYP_Topen] = (tmessage_handler)handle_Topen, - [LIB9P_TYP_Tcreate] = (tmessage_handler)handle_Tcreate, - [LIB9P_TYP_Tread] = (tmessage_handler)handle_Tread, - [LIB9P_TYP_Twrite] = (tmessage_handler)handle_Twrite, - [LIB9P_TYP_Tclunk] = (tmessage_handler)handle_Tclunk, - [LIB9P_TYP_Tremove] = (tmessage_handler)handle_Tremove, - [LIB9P_TYP_Tstat] = (tmessage_handler)handle_Tstat, - [LIB9P_TYP_Twstat] = (tmessage_handler)handle_Twstat, -#if CONFIG_9P_ENABLE_9P2000_e - [LIB9P_TYP_Tsession] = (tmessage_handler)handle_Tsession, - [LIB9P_TYP_Tsread] = (tmessage_handler)handle_Tsread, - [LIB9P_TYP_Tswrite] = (tmessage_handler)handle_Tswrite, -#endif -}; +typedef void (*tmessage_handler)(struct srv_req *, void *); -static void handle_message(struct _lib9p_srv_req *ctx) { +void lib9p_srv_worker(struct srv_req *ctx) { uint8_t *host_req = NULL; - uint8_t host_resp[CONFIG_9P_SRV_MAX_HOSTMSG_SIZE]; - /* Unmarshal it. */ - ssize_t host_size = lib9p_Tmsg_validate(&ctx->ctx.basectx, ctx->net_bytes); - if (host_size < 0) - goto write; + /* Unmarshal it. *****************************************************/ + ssize_t host_size = lib9p_Tmsg_validate(&ctx->basectx, ctx->net_bytes); + if (host_size < 0) { + srv_respond_error(ctx); + goto release; + } host_req = calloc(1, host_size); assert(host_req); enum lib9p_msg_type typ; - lib9p_Tmsg_unmarshal(&ctx->ctx.basectx, ctx->net_bytes, - &typ, host_req); -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wformat" -#pragma GCC diagnostic ignored "-Wformat-extra-args" - infof("> %v", lo_box_lib9p_msg_as_fmt_formatter(&ctx->ctx.basectx, typ, host_req)); -#pragma GCC diagnostic pop + lib9p_Tmsg_unmarshal(&ctx->basectx, ctx->net_bytes, + &typ, host_req); + srv_msglog(ctx, typ, host_req); - /* Handle it. */ - tmessage_handlers[typ](ctx, (void *)host_req, (void *)host_resp); - - write: - if (lib9p_ctx_has_error(&ctx->ctx.basectx)) - respond_error(ctx); - else { - struct lib9p_Rmsg_send_buf net_resp; - if (lib9p_Rmsg_marshal(&ctx->ctx.basectx, - typ+1, host_resp, - &net_resp)) - goto write; + /* Handle it. ********************************************************/ + tmessage_handler handler; #pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wformat" -#pragma GCC diagnostic ignored "-Wformat-extra-args" - infof("< %v", lo_box_lib9p_msg_as_fmt_formatter(&ctx->ctx.basectx, typ+1, &host_resp)); +#pragma GCC diagnostic ignored "-Wswitch-enum" + switch (typ) { + case LIB9P_TYP_Tversion: handler = (tmessage_handler)handle_Tversion; break; +#if _LIB9P_ENABLE_stat + case LIB9P_TYP_Tauth: handler = (tmessage_handler)handle_Tauth; break; + case LIB9P_TYP_Tattach: handler = (tmessage_handler)handle_Tattach; break; + case LIB9P_TYP_Tflush: handler = (tmessage_handler)handle_Tflush; break; + case LIB9P_TYP_Twalk: handler = (tmessage_handler)handle_Twalk; break; + case LIB9P_TYP_Topen: handler = (tmessage_handler)handle_Topen; break; + case LIB9P_TYP_Tcreate: handler = (tmessage_handler)handle_Tcreate; break; + case LIB9P_TYP_Tread: handler = (tmessage_handler)handle_Tread; break; + case LIB9P_TYP_Twrite: handler = (tmessage_handler)handle_Twrite; break; + case LIB9P_TYP_Tclunk: handler = (tmessage_handler)handle_Tclunk; break; + case LIB9P_TYP_Tremove: handler = (tmessage_handler)handle_Tremove; break; + case LIB9P_TYP_Tstat: handler = (tmessage_handler)handle_Tstat; break; + case LIB9P_TYP_Twstat: handler = (tmessage_handler)handle_Twstat; break; +#endif +#if CONFIG_9P_ENABLE_9P2000_p9p + case LIB9P_TYP_Topenfd: handler = (tmessage_handler)handle_Topenfd; break; +#endif +#if CONFIG_9P_ENABLE_9P2000_e + case LIB9P_TYP_Tsession: handler = (tmessage_handler)handle_Tsession; break; + case LIB9P_TYP_Tsread: handler = (tmessage_handler)handle_Tsread; break; + case LIB9P_TYP_Tswrite: handler = (tmessage_handler)handle_Tswrite; break; +#endif + default: + assert_notreached("lib9p_Tmsg_validate() should have rejected unknown typ"); + } #pragma GCC diagnostic pop - write_Rmsg(ctx, &net_resp); + handler(ctx, (void *)host_req); + assert(ctx->responded); + + /* Release resources. ************************************************/ + release: + map_del(&ctx->parent_sess->reqs, ctx->tag); + size_t nwaiters; + while ((nwaiters = cr_chan_num_waiters(&ctx->flush_ch))) { + cr_chan_send(&ctx->flush_ch, (nwaiters == 1) + ? _LIB9P_SRV_FLUSH_RFLUSH + : _LIB9P_SRV_FLUSH_SILENT); } + if (ctx->parent_sess->closing && !map_len(&ctx->parent_sess->reqs)) + cr_unpause(ctx->parent_sess->parent_conn->reader); if (host_req) free(host_req); free(ctx->net_bytes); } -#define util_handler_common(ctx, req, resp) do { \ - assert(ctx); \ - assert(req); \ - assert(resp); \ - resp->tag = req->tag; \ - } while (0) - -static inline bool srv_util_check_perm(struct _lib9p_srv_req *ctx, struct lib9p_stat *stat, uint8_t action) { - assert(ctx); - assert(stat); - assert(action); - - /* TODO actually check user and group instead of just assuming "other". */ - uint8_t mode = (uint8_t)(stat->file_mode & 07); - - return mode & action; -} - -/** - * Ensures that `file` is saved into the pathmap, and increments the - * gc_refcount by 1 (for presumptive insertion into the fidmap). - * parent_path's gc_refcount is also incremented as appropriate. - * - * Returns a pointer to the stored pathinfo. - */ -static inline struct srv_pathinfo *srv_util_pathsave(struct _lib9p_srv_req *ctx, - lo_interface lib9p_srv_file file, - srv_path_t parent_path) { - assert(ctx); - assert(!LO_IS_NULL(file)); - - struct lib9p_qid qid = LO_CALL(file, qid); - struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, qid.path); - if (pathinfo) - assert(LO_EQ(pathinfo->file, file)); - else { - pathinfo = pathmap_store(&ctx->parent_sess->paths, qid.path, - (struct srv_pathinfo){ - .file = file, - .parent_dir = parent_path, - .gc_refcount = 0, - .io_refcount = 0, - }); - assert(pathinfo); - if (parent_path != qid.path) { - struct srv_pathinfo *parent = pathmap_load(&ctx->parent_sess->paths, parent_path); - assert(parent); - parent->gc_refcount++; - } - } - pathinfo->gc_refcount++; - return pathinfo; -} - -/** - * Decrement the path's gc_refcount, and trigger garbage collection as - * appropriate. - */ -static inline void srv_util_pathfree(struct _lib9p_srv_req *ctx, srv_path_t path) { - assert(ctx); - - struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, path); - assert(pathinfo); - pathinfo->gc_refcount--; - if (pathinfo->gc_refcount == 0) { - if (pathinfo->parent_dir != path) - srv_util_pathfree(ctx, pathinfo->parent_dir); - LO_CALL(pathinfo->file, free); - pathmap_del(&ctx->parent_sess->paths, path); - } -} - -static inline bool srv_util_pathisdir(struct srv_pathinfo *pathinfo) { - assert(pathinfo); - return LO_CALL(pathinfo->file, qid).type & LIB9P_QT_DIR; -} - -/** - * Store fid as pointing to pathinfo. Assumes that - * pathinfo->gc_refcount has already been incremented; does *not* - * decrement it on failure. - */ -static inline struct _srv_fidinfo *srv_util_fidsave(struct _lib9p_srv_req *ctx, lib9p_fid_t fid, struct srv_pathinfo *pathinfo, bool overwrite) { - assert(ctx); - assert(fid != LIB9P_FID_NOFID); - assert(pathinfo); - - struct lib9p_qid qid = LO_CALL(pathinfo->file, qid); - - struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, fid); - if (fidinfo) { - if (overwrite) { - struct srv_pathinfo *old_pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path); - assert(old_pathinfo); - if (srv_util_pathisdir(old_pathinfo)) - LO_CALL(fidinfo->dir.io, iofree); - else - LO_CALL(fidinfo->file.io, iofree); - srv_util_pathfree(ctx, fidinfo->path); - } else { - lib9p_error(&ctx->ctx.basectx, - LINUX_EBADF, "FID already in use"); - return NULL; - } +static inline void _srv_respond(struct srv_req *ctx, enum lib9p_msg_type resp_typ, void *host_resp) { + assert(!ctx->responded); + if (lib9p_ctx_has_error(&ctx->basectx)) { + error: + srv_respond_error(ctx); + } else if (ctx->flush_acknowledged) { + /* do nothing */ } else { - fidinfo = fidmap_store(&ctx->parent_sess->fids, fid, (struct _srv_fidinfo){}); - if (!fidinfo) { - lib9p_error(&ctx->ctx.basectx, - LINUX_EMFILE, "too many open files"); - return NULL; - } + assert(host_resp); + struct lib9p_Rmsg_send_buf net_resp; + if (lib9p_Rmsg_marshal(&ctx->basectx, + resp_typ, host_resp, + &net_resp)) + goto error; + srv_msglog(ctx, resp_typ, host_resp); + srv_write_Rmsg(ctx, &net_resp); } - *fidinfo = (struct _srv_fidinfo){ - .path = qid.path, - }; - return fidinfo; + ctx->responded = true; } +#define srv_respond(CTX, TYP, HOST_RESP) do { \ + struct lib9p_msg_R##TYP *_host_resp = HOST_RESP; \ + _srv_respond(CTX, LIB9P_TYP_R##TYP, _host_resp); \ +} while (0) + +/* handle_T* ******************************************************************/ +#define srv_handler_common(ctx, typ, req) \ + assert(ctx); \ + assert(req); \ + struct lib9p_msg_T##typ *_typecheck_req [[gnu::unused]] = req; \ + struct lib9p_msg_R##typ resp = { .tag = ctx->tag } -static void handle_Tversion(struct _lib9p_srv_req *ctx, - struct lib9p_msg_Tversion *req, - struct lib9p_msg_Rversion *resp) { - util_handler_common(ctx, req, resp); +static void handle_Tversion(struct srv_req *ctx, + struct lib9p_msg_Tversion *req) { + srv_handler_common(ctx, version, req); enum lib9p_version version = LIB9P_VER_unknown; @@ -594,7 +730,14 @@ static void handle_Tversion(struct _lib9p_srv_req *ctx, '0' <= req->version.utf8[4] && req->version.utf8[4] <= '9' && '0' <= req->version.utf8[5] && req->version.utf8[5] <= '9' && (req->version.len == 6 || req->version.utf8[6] == '.')) { +#if CONFIG_9P_ENABLE_9P2000 version = LIB9P_VER_9P2000; +#endif +#if CONFIG_9P_ENABLE_9P2000_p9p + struct lib9p_srv *srv = ctx->parent_sess->parent_conn->parent_srv; + if (srv->type_assert_unix && !LO_IS_NULL(srv->type_assert_unix(ctx->parent_sess->parent_conn->fd))) + version = LIB9P_VER_9P2000_p9p; +#endif #if CONFIG_9P_ENABLE_9P2000_u if (lib9p_str_eq(lib9p_str_sliceleft(req->version, 6), lib9p_str(".u"))) version = LIB9P_VER_9P2000_u; @@ -607,292 +750,321 @@ static void handle_Tversion(struct _lib9p_srv_req *ctx, uint32_t min_msg_size = lib9p_version_min_msg_size(version); if (req->max_msg_size < min_msg_size) { - lib9p_errorf(&ctx->ctx.basectx, - LINUX_EDOM, "requested max_msg_size is less than minimum for %s (%"PRIu32" < %"PRIu32")", + lib9p_errorf(&ctx->basectx, + LIB9P_ERRNO_L_EDOM, "requested max_msg_size is less than minimum for %s (%"PRIu32" < %"PRIu32")", lib9p_version_str(version), req->max_msg_size, min_msg_size); - return; + goto tversion_return; } - resp->version = lib9p_str((char *)lib9p_version_str(version)); /* cast to discard "const" qualifier */ - resp->max_msg_size = (CONFIG_9P_SRV_MAX_MSG_SIZE < req->max_msg_size) + resp.version = lib9p_str((char *)lib9p_version_str(version)); /* cast to discard "const" qualifier */ +#if CONFIG_9P_ENABLE_9P2000_p9p + if (version == LIB9P_VER_9P2000_p9p) + resp.version = lib9p_str("9P2000"); +#endif + resp.max_msg_size = (CONFIG_9P_SRV_MAX_MSG_SIZE < req->max_msg_size) ? CONFIG_9P_SRV_MAX_MSG_SIZE : req->max_msg_size; /* Close the old session. */ - if (ctx->parent_sess->reqs.len) { + if (map_len(&ctx->parent_sess->reqs)) { /* Flush all in-progress requests, and wait for them * to finish. */ - struct cr_select_arg *list = alloca(sizeof(struct cr_select_arg) * ctx->parent_sess->reqs.len); - while (ctx->parent_sess->reqs.len) { - uint16_t tag [[gnu::unused]]; - struct _lib9p_srv_req **reqpp; + struct cr_select_arg *args = stack_alloc(map_len(&ctx->parent_sess->reqs), struct cr_select_arg); + while (map_len(&ctx->parent_sess->reqs)) { size_t i = 0; - bool flushed; MAP_FOREACH(&ctx->parent_sess->reqs, tag, reqpp) { - list[i] = CR_SELECT_RECV(&((*reqpp)->ctx._flushch), &flushed); + enum _lib9p_srv_flush_result flushed; + args[i++] = CR_SELECT_RECV(&((*reqpp)->flush_ch), &flushed); } - cr_select_v(i, list); + assert(i == map_len(&ctx->parent_sess->reqs)); + cr_select_v(i, args); } } - if (ctx->parent_sess->fids.len) { - /* Close all FIDs. */ - uint32_t fid; - struct _srv_fidinfo *fidinfo [[gnu::unused]]; - MAP_FOREACH(&ctx->parent_sess->fids, fid, fidinfo) { - handle_Tclunk(ctx, - &(struct lib9p_msg_Tclunk){.fid = fid}, - &(struct lib9p_msg_Rclunk){}); + /* Close all FIDs. */ + MAP_FOREACH(&ctx->parent_sess->fids, fid, fidinfo) { + srv_fid_del(ctx, fid, fidinfo, false); + if (lib9p_ctx_has_error(&ctx->basectx)) { + srv_nonrespond_errorf("clunk: %.*s", CONFIG_9P_MAX_ERR_SIZE, ctx->basectx.err_msg); + lib9p_ctx_clear_error(&ctx->basectx); } } /* Replace the old session with the new session. */ ctx->parent_sess->version = version; - ctx->parent_sess->max_msg_size = resp->max_msg_size; - ctx->parent_sess->rerror_overhead = min_msg_size; + ctx->parent_sess->max_msg_size = resp.max_msg_size; + + tversion_return: + srv_respond(ctx, version, &resp); } -static void handle_Tauth(struct _lib9p_srv_req *ctx, - struct lib9p_msg_Tauth *req, - struct lib9p_msg_Rauth *resp) { - util_handler_common(ctx, req, resp); +#if _LIB9P_ENABLE_stat +static void handle_Tauth(struct srv_req *ctx, + struct lib9p_msg_Tauth *req) { + srv_handler_common(ctx, auth, req); - ctx->ctx.uid = req->n_uid; - ctx->ctx.uname = req->uname; struct lib9p_srv *srv = ctx->parent_sess->parent_conn->parent_srv; - if (!srv->auth) { - lib9p_error(&ctx->ctx.basectx, - LINUX_EOPNOTSUPP, "authentication not required"); - return; + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EOPNOTSUPP, "authentication not required"); + goto tauth_return; } - srv->auth(&ctx->ctx, req->aname); - lib9p_error(&ctx->ctx.basectx, - LINUX_EOPNOTSUPP, "TODO: auth not implemented"); + ctx->user = srv_userid_new(req->uname, req->unum); + + srv->auth(ctx, req->aname); + + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EOPNOTSUPP, "TODO: auth not implemented"); + + if (lib9p_ctx_has_error(&ctx->basectx)) + ctx->user = srv_userid_decref(ctx->user); + + tauth_return: + srv_respond(ctx, auth, &resp); } -static void handle_Tattach(struct _lib9p_srv_req *ctx, - struct lib9p_msg_Tattach *req, - struct lib9p_msg_Rattach *resp) { - util_handler_common(ctx, req, resp); +static void handle_Tattach(struct srv_req *ctx, + struct lib9p_msg_Tattach *req) { + srv_handler_common(ctx, attach, req); - ctx->ctx.uid = req->n_uid; - ctx->ctx.uname = req->uname; - struct lib9p_srv *srv = ctx->parent_sess->parent_conn->parent_srv; + if (req->fid == LIB9P_FID_NOFID) { + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EBADF, "cannot assign to NOFID"); + goto tattach_return; + } + struct lib9p_srv *srv = ctx->parent_sess->parent_conn->parent_srv; if (srv->auth) { - /* - struct lib9p_srv_filehandle *fh = fidmap_get(req->afid); - if (!fh) - lib9p_error(&ctx->ctx.basectx, - LINUX_EACCES, "FID provided as auth-file is not a valid FID"); - else if (fh->type != FH_AUTH) - lib9p_error(&ctx->ctx.basectx, - LINUX_EACCES, "FID provided as auth-file is not an auth-file"); - else if (!lib9p_str_eq(fh->data.auth.uname, req->uname)) - lib9p_errorf(&ctx->ctx.basectx, - LINUX_EACCES, "FID provided as auth-file is for user=\"%.*s\" and cannot be used for user=\"%.*s\"", - fh->data.auth.uname.len, fh->data.auth.uname.utf8, + struct srv_fidinfo *afid = map_load(&ctx->parent_sess->fids, req->afid); + if (!afid) + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EACCES, "FID provided as auth-file is not a valid FID"); + else if (afid->type != SRV_FILETYPE_AUTH) + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EACCES, "FID provided as auth-file is not an auth-file"); + else if (!lib9p_str_eq(afid->user->name, req->uname)) + lib9p_errorf(&ctx->basectx, + LIB9P_ERRNO_L_EACCES, "FID provided as auth-file is for user=\"%.*s\" and cannot be used for user=\"%.*s\"", + afid->user->name.len, afid->user->name.utf8, req->uname.len, req->uname.utf8); - else if (!lib9p_str_eq(fh->data.auth.aname, req->aname)) - lib9p_errorf(&ctx->ctx.basectx, - LINUX_EACCES, "FID provided as auth-file is for tree=\"%.*s\" and cannot be used for tree=\"%.*s\"", - fh->data.auth.aname.len, fh->data.auth.aname.utf8, +#if CONFIG_9P_ENABLE_9P2000_u || CONFIG_9P_ENABLE_9P2000_L + else if (afid->user->num != req->unum) + lib9p_errorf(&ctx->basectx, + LIB9P_ERRNO_L_EACCES, "FID provided as auth-file is for user=%"PRIu32" and cannot be used for user=%"PRIu32, + afid->user->num, req->unum); +#endif + else if (!lib9p_str_eq(afid->auth.aname, req->aname)) + lib9p_errorf(&ctx->basectx, + LIB9P_ERRNO_L_EACCES, "FID provided as auth-file is for tree=\"%.*s\" and cannot be used for tree=\"%.*s\"", + afid->auth.aname.len, afid->auth.aname.utf8, req->aname.len, req->aname.utf8); - else if (!fh->data.auth.authenticated) - lib9p_error(&ctx->ctx.basectx, - LINUX_EACCES, "FID provided as auth-file has not completed authentication"); - fh->refcount--; - if (lib9p_ctx_has_error(&ctx->ctx)) - return; - */ - lib9p_error(&ctx->ctx.basectx, - LINUX_EOPNOTSUPP, "TODO: auth not (yet?) implemented"); - return; + else if (!afid->auth.completed) + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EACCES, "FID provided as auth-file has not completed authentication"); + if (lib9p_ctx_has_error(&ctx->basectx)) + goto tattach_return; + ctx->user = srv_userid_incref(afid->user); } else { if (req->afid != LIB9P_FID_NOFID) { - lib9p_error(&ctx->ctx.basectx, - LINUX_EACCES, "FID provided as auth-file, but no auth-file is required"); - return; + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EACCES, "FID provided as auth-file, but no auth-file is required"); + goto tattach_return; } - } - - if (req->fid == LIB9P_FID_NOFID) { - lib9p_error(&ctx->ctx.basectx, - LINUX_EBADF, "cannot assign to NOFID"); - return; + ctx->user = srv_userid_new(req->uname, req->unum); } /* 1. File object */ - lo_interface lib9p_srv_file root_file = srv->rootdir(&ctx->ctx, req->aname); - assert(LO_IS_NULL(root_file) == lib9p_ctx_has_error(&ctx->ctx.basectx)); - if (lib9p_ctx_has_error(&ctx->ctx.basectx)) - return; + lo_interface lib9p_srv_file root_file = srv->rootdir(ctx, req->aname); + assert(LO_IS_NULL(root_file) == lib9p_ctx_has_error(&ctx->basectx)); + if (lib9p_ctx_has_error(&ctx->basectx)) + goto tattach_return; struct lib9p_qid root_qid = LO_CALL(root_file, qid); - assert(root_qid.type & LIB9P_QT_DIR); + assert(srv_qid_filetype(root_qid) == SRV_FILETYPE_DIR); /* 2. pathinfo */ - struct srv_pathinfo *root_pathinfo = srv_util_pathsave(ctx, root_file, root_qid.path); + struct srv_pathinfo *root_pathinfo = srv_path_save(ctx, root_file, root_qid.path); /* 3. fidinfo */ - if (!srv_util_fidsave(ctx, req->fid, root_pathinfo, false)) { - srv_util_pathfree(ctx, root_qid.path); - return; + if (!srv_fid_store(ctx, req->fid, root_pathinfo, false)) { + srv_path_decref(ctx, root_qid.path); + goto tattach_return; } - resp->qid = root_qid; - return; + resp.qid = root_qid; + tattach_return: + if (ctx->user) + ctx->user = srv_userid_decref(ctx->user); + srv_respond(ctx, attach, &resp); } -static void handle_Tflush(struct _lib9p_srv_req *ctx, - struct lib9p_msg_Tflush *req, - struct lib9p_msg_Rflush *resp) { - util_handler_common(ctx, req, resp); - - struct _lib9p_srv_req **oldreqp = reqmap_load(&ctx->parent_sess->reqs, req->oldtag); - if (oldreqp) - _lib9p_srv_flushch_recv(&((*oldreqp)->ctx._flushch)); +static void handle_Tflush(struct srv_req *ctx, + struct lib9p_msg_Tflush *req) { + srv_handler_common(ctx, flush, req); + + struct srv_req **oldreqp = map_load(&ctx->parent_sess->reqs, req->oldtag); + if (oldreqp) { + struct srv_req *oldreq = *oldreqp; + enum _lib9p_srv_flush_result res = _LIB9P_SRV_FLUSH_RFLUSH; + switch (cr_select_l(CR_SELECT_RECV(&oldreq->flush_ch, &res), + CR_SELECT_SEND(&ctx->flush_ch, &res))) { + case 0: /* original request returned */ + req_debugf("original request (tag=%"PRIu16") returned", req->oldtag); + ctx->flush_acknowledged = (res == _LIB9P_SRV_FLUSH_SILENT); + break; + case 1: /* flush itself got flushed */ + req_debugf("flush itself flushed"); + ctx->flush_acknowledged = true; + break; + } + } + srv_respond(ctx, flush, &resp); } -static void handle_Twalk(struct _lib9p_srv_req *ctx, - struct lib9p_msg_Twalk *req, - struct lib9p_msg_Rwalk *resp) { - util_handler_common(ctx, req, resp); +static void handle_Twalk(struct srv_req *ctx, + struct lib9p_msg_Twalk *req) { + srv_handler_common(ctx, walk, req); if (req->newfid == LIB9P_FID_NOFID) { - lib9p_error(&ctx->ctx.basectx, - LINUX_EBADF, "cannot assign to NOFID"); - return; + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EBADF, "cannot assign to NOFID"); + goto twalk_return; } - struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, req->fid); + struct srv_fidinfo *fidinfo = map_load(&ctx->parent_sess->fids, req->fid); if (!fidinfo) { - lib9p_errorf(&ctx->ctx.basectx, - LINUX_EBADF, "bad file number %"PRIu32, req->fid); - return; + lib9p_errorf(&ctx->basectx, + LIB9P_ERRNO_L_EBADF, "bad file number %"PRIu32, req->fid); + goto twalk_return; + } + if (fidinfo->flags & FIDFLAG_OPEN) { + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EALREADY, "cannot walk on FID open for I/O"); + goto twalk_return; } + ctx->user = srv_userid_incref(fidinfo->user); - struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path); + struct srv_pathinfo *pathinfo = map_load(&ctx->parent_sess->paths, fidinfo->path); assert(pathinfo); pathinfo->gc_refcount++; - resp->wqid = (struct lib9p_qid *)(&resp[1]); - for (resp->nwqid = 0; resp->nwqid < req->nwname; resp->nwqid++) { + struct lib9p_qid _resp_qid[16]; + resp.wqid = _resp_qid; + for (resp.nwqid = 0; resp.nwqid < req->nwname; resp.nwqid++) { + if (pathinfo->type != SRV_FILETYPE_DIR) { + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_ENOTDIR, "not a directory"); + break; + } + struct srv_pathinfo *new_pathinfo; - if (lib9p_str_eq(req->wname[resp->nwqid], lib9p_str(".."))) { - new_pathinfo = pathmap_load(&ctx->parent_sess->paths, pathinfo->parent_dir); + if (lib9p_str_eq(req->wname[resp.nwqid], lib9p_str(".."))) { + new_pathinfo = map_load(&ctx->parent_sess->paths, pathinfo->parent_dir); assert(new_pathinfo); new_pathinfo->gc_refcount++; } else { - if (!srv_util_pathisdir(pathinfo)) { - lib9p_error(&ctx->ctx.basectx, - LINUX_ENOTDIR, "not a directory"); - break; - } - - lo_interface lib9p_srv_file member_file = LO_CALL(pathinfo->file, dwalk, &ctx->ctx, req->wname[resp->nwqid]); - assert(LO_IS_NULL(member_file) == lib9p_ctx_has_error(&ctx->ctx.basectx)); - if (lib9p_ctx_has_error(&ctx->ctx.basectx)) + lo_interface lib9p_srv_file member_file = LO_CALL(pathinfo->file, dwalk, ctx, req->wname[resp.nwqid]); + assert(LO_IS_NULL(member_file) == lib9p_ctx_has_error(&ctx->basectx)); + if (lib9p_ctx_has_error(&ctx->basectx)) break; - new_pathinfo = srv_util_pathsave(ctx, member_file, LO_CALL(pathinfo->file, qid).path); + new_pathinfo = srv_path_save(ctx, member_file, LO_CALL(pathinfo->file, qid).path); + assert(new_pathinfo); } - if (srv_util_pathisdir(new_pathinfo)) { - struct lib9p_stat stat = LO_CALL(new_pathinfo->file, stat, &ctx->ctx); - if (lib9p_ctx_has_error(&ctx->ctx.basectx)) + if (new_pathinfo->type == SRV_FILETYPE_DIR) { + struct lib9p_srv_stat stat = LO_CALL(new_pathinfo->file, stat, ctx); + if (lib9p_ctx_has_error(&ctx->basectx)) break; - lib9p_stat_assert(stat); - if (!srv_util_check_perm(ctx, &stat, 0b001)) { - lib9p_error(&ctx->ctx.basectx, - LINUX_EACCES, "you do not have execute permission on that directory"); - srv_util_pathfree(ctx, LO_CALL(new_pathinfo->file, qid).path); + lib9p_srv_stat_assert(stat); + if (!srv_check_perm(ctx, &stat, 0b001)) { + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EACCES, "you do not have execute permission on that directory"); + srv_path_decref(ctx, LO_CALL(new_pathinfo->file, qid).path); break; } } - resp->wqid[resp->nwqid] = LO_CALL(new_pathinfo->file, qid); + resp.wqid[resp.nwqid] = LO_CALL(new_pathinfo->file, qid); - srv_util_pathfree(ctx, LO_CALL(pathinfo->file, qid).path); + srv_path_decref(ctx, LO_CALL(pathinfo->file, qid).path); pathinfo = new_pathinfo; } - if (resp->nwqid == req->nwname) { - if (req->newfid == req->fid) { - if (srv_util_pathisdir(pathinfo)) - LO_CALL(fidinfo->dir.io, iofree); - else - LO_CALL(fidinfo->file.io, iofree); - fidinfo->flags = 0; - } - if (!srv_util_fidsave(ctx, req->newfid, pathinfo, req->newfid == req->fid)) - srv_util_pathfree(ctx, LO_CALL(pathinfo->file, qid).path); + if (resp.nwqid == req->nwname) { + if (!srv_fid_store(ctx, req->newfid, pathinfo, req->newfid == req->fid)) + srv_path_decref(ctx, LO_CALL(pathinfo->file, qid).path); } else { - assert(lib9p_ctx_has_error(&ctx->ctx.basectx)); - srv_util_pathfree(ctx, LO_CALL(pathinfo->file, qid).path); - if (resp->nwqid > 0) - lib9p_ctx_clear_error(&ctx->ctx.basectx); + assert(lib9p_ctx_has_error(&ctx->basectx)); + srv_path_decref(ctx, LO_CALL(pathinfo->file, qid).path); + if (resp.nwqid > 0) + lib9p_ctx_clear_error(&ctx->basectx); } + twalk_return: + if (ctx->user) + ctx->user = srv_userid_decref(ctx->user); + srv_respond(ctx, walk, &resp); } -static void handle_Topen(struct _lib9p_srv_req *ctx, - struct lib9p_msg_Topen *req, - struct lib9p_msg_Ropen *resp) { - util_handler_common(ctx, req, resp); +static void handle_Topen(struct srv_req *ctx, + struct lib9p_msg_Topen *req) { + srv_handler_common(ctx, open, req); /* Check that the FID is valid for this. */ - struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, req->fid); + struct srv_fidinfo *fidinfo = map_load(&ctx->parent_sess->fids, req->fid); if (!fidinfo) { - lib9p_errorf(&ctx->ctx.basectx, - LINUX_EBADF, "bad file number %"PRIu32, req->fid); - return; + lib9p_errorf(&ctx->basectx, + LIB9P_ERRNO_L_EBADF, "bad file number %"PRIu32, req->fid); + goto topen_return; } if (fidinfo->flags & FIDFLAG_OPEN) { - lib9p_error(&ctx->ctx.basectx, - LINUX_EALREADY, "FID is already open"); - return; + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EALREADY, "FID is already open"); + goto topen_return; } - struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path); - assert(pathinfo); - if (srv_util_pathisdir(pathinfo)) { + if (fidinfo->type == SRV_FILETYPE_DIR) { if ( ((req->mode & LIB9P_O_MODE_MASK) != LIB9P_O_MODE_READ) || (req->mode & LIB9P_O_TRUNC) || (req->mode & LIB9P_O_RCLOSE) ) { - lib9p_error(&ctx->ctx.basectx, - LINUX_EISDIR, "directories cannot be written, executed, truncated, or removed-on-close"); - return; + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EISDIR, "directories cannot be written, executed, truncated, or removed-on-close"); + goto topen_return; } } + ctx->user = srv_userid_incref(fidinfo->user); /* Variables. */ - lib9p_o_t reqmode = req->mode; - uint8_t fidflags = fidinfo->flags; + lib9p_o_t reqmode = req->mode; + uint8_t fidflags = fidinfo->flags; + struct srv_pathinfo *pathinfo = map_load(&ctx->parent_sess->paths, fidinfo->path); + assert(pathinfo); /* Check permissions. */ if (reqmode & LIB9P_O_RCLOSE) { - struct srv_pathinfo *parent = pathmap_load(&ctx->parent_sess->paths, pathinfo->parent_dir); + struct srv_pathinfo *parent = map_load(&ctx->parent_sess->paths, pathinfo->parent_dir); assert(parent); - struct lib9p_stat parent_stat = LO_CALL(parent->file, stat, &ctx->ctx); - if (lib9p_ctx_has_error(&ctx->ctx.basectx)) - return; - lib9p_stat_assert(parent_stat); - if (!srv_util_check_perm(ctx, &parent_stat, 0b010)) { - lib9p_error(&ctx->ctx.basectx, - LINUX_EACCES, "permission denied to remove-on-close"); - return; + struct lib9p_srv_stat parent_stat = LO_CALL(parent->file, stat, ctx); + if (lib9p_ctx_has_error(&ctx->basectx)) + goto topen_return; + lib9p_srv_stat_assert(parent_stat); + if (!srv_check_perm(ctx, &parent_stat, 0b010)) { + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EACCES, "permission denied to remove-on-close"); + goto topen_return; } fidflags |= FIDFLAG_RCLOSE; } - struct lib9p_stat stat = LO_CALL(pathinfo->file, stat, &ctx->ctx); - if (lib9p_ctx_has_error(&ctx->ctx.basectx)) - return; - lib9p_stat_assert(stat); - if ((stat.file_mode & LIB9P_DM_EXCL) && pathinfo->io_refcount) { - lib9p_error(&ctx->ctx.basectx, - LINUX_EEXIST, "exclusive file is already opened"); - return; + struct lib9p_srv_stat stat = LO_CALL(pathinfo->file, stat, ctx); + if (lib9p_ctx_has_error(&ctx->basectx)) + goto topen_return; + lib9p_srv_stat_assert(stat); + if ((stat.mode & LIB9P_DM_EXCL) && pathinfo->io_refcount) { + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EEXIST, "exclusive file is already opened"); + goto topen_return; } - if (stat.file_mode & LIB9P_DM_APPEND) + if (stat.mode & LIB9P_DM_APPEND) { + fidflags |= FIDFLAG_APPEND; reqmode = reqmode & ~LIB9P_O_TRUNC; + } uint8_t perm_bits = 0; bool rd = false, wr = false; switch (reqmode & LIB9P_O_MODE_MASK) { @@ -913,33 +1085,42 @@ static void handle_Topen(struct _lib9p_srv_req *ctx, rd = true; break; } - if (!srv_util_check_perm(ctx, &stat, perm_bits)) { - lib9p_error(&ctx->ctx.basectx, - LINUX_EACCES, "permission denied"); - return; + if (!srv_check_perm(ctx, &stat, perm_bits)) { + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EACCES, "permission denied"); + goto topen_return; } /* Actually make the call. */ uint32_t iounit; struct lib9p_qid qid; - if (srv_util_pathisdir(pathinfo)) { - fidinfo->dir.io = LO_CALL(pathinfo->file, dopen, &ctx->ctx); - assert(LO_IS_NULL(fidinfo->dir.io) == lib9p_ctx_has_error(&ctx->ctx.basectx)); - if (lib9p_ctx_has_error(&ctx->ctx.basectx)) - return; + switch (pathinfo->type) { + case SRV_FILETYPE_DIR: + fidinfo->dir.io = LO_CALL(pathinfo->file, dopen, ctx); + assert(LO_IS_NULL(fidinfo->dir.io) == lib9p_ctx_has_error(&ctx->basectx)); + if (lib9p_ctx_has_error(&ctx->basectx)) + goto topen_return; fidinfo->dir.idx = 0; fidinfo->dir.off = 0; qid = LO_CALL(fidinfo->dir.io, qid); iounit = 0; - } else { - fidinfo->file.io = LO_CALL(pathinfo->file, fopen, &ctx->ctx, + break; + case SRV_FILETYPE_FILE: + fidinfo->file.io = LO_CALL(pathinfo->file, fopen, ctx, rd, wr, reqmode & LIB9P_O_TRUNC); - assert(LO_IS_NULL(fidinfo->file.io) == lib9p_ctx_has_error(&ctx->ctx.basectx)); - if (lib9p_ctx_has_error(&ctx->ctx.basectx)) - return; + assert(LO_IS_NULL(fidinfo->file.io) == lib9p_ctx_has_error(&ctx->basectx)); + if (lib9p_ctx_has_error(&ctx->basectx)) + goto topen_return; qid = LO_CALL(fidinfo->file.io, qid); iounit = LO_CALL(fidinfo->file.io, iounit); + break; + case SRV_FILETYPE_AUTH: + assert_notreached("TODO: auth not yet implemented"); + break; + default: + assert_notreached("invalid srv_filetype"); + break; } /* Success. */ @@ -949,222 +1130,326 @@ static void handle_Topen(struct _lib9p_srv_req *ctx, fidflags |= FIDFLAG_OPEN_W; pathinfo->io_refcount++; fidinfo->flags = fidflags; - resp->qid = qid; - resp->iounit = iounit; + resp.qid = qid; + resp.iounit = iounit; + topen_return: + if (ctx->user) + ctx->user = srv_userid_decref(ctx->user); + srv_respond(ctx, open, &resp); } -static void handle_Tcreate(struct _lib9p_srv_req *ctx, - struct lib9p_msg_Tcreate *req, - struct lib9p_msg_Rcreate *resp) { - util_handler_common(ctx, req, resp); +static void handle_Tcreate(struct srv_req *ctx, + struct lib9p_msg_Tcreate *req) { + srv_handler_common(ctx, create, req); + + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EOPNOTSUPP, "create not (yet?) implemented"); - lib9p_error(&ctx->ctx.basectx, - LINUX_EOPNOTSUPP, "create not (yet?) implemented"); + srv_respond(ctx, create, &resp); } -static void handle_Tread(struct _lib9p_srv_req *ctx, - struct lib9p_msg_Tread *req, - struct lib9p_msg_Rread *resp) { - util_handler_common(ctx, req, resp); +static inline struct lib9p_stat srv_stat_to_net_stat(struct lib9p_srv_stat in) { + return (struct lib9p_stat){ + .qid = in.qid, + .mode = in.mode, + .atime = in.atime_sec, + .mtime = in.mtime_sec, + .length = in.size, + .name = in.name, + .owner_uname = in.owner_uid.name, + .owner_gname = in.owner_gid.name, + .last_modifier_uname = in.last_modifier_uid.name, +#if CONFIG_9P_ENABLE_9P2000_u + .owner_unum = in.owner_uid.num, + .owner_gnum = in.owner_gid.num, + .last_modifier_unum = in.last_modifier_uid.num, + .extension = in.extension, +#endif + }; +} + +static void handle_Tread(struct srv_req *ctx, + struct lib9p_msg_Tread *req) { + srv_handler_common(ctx, read, req); + char *heap = NULL; /* TODO: serialize simultaneous reads to the same FID */ /* Check that the FID is valid for this. */ - struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, req->fid); + struct srv_fidinfo *fidinfo = map_load(&ctx->parent_sess->fids, req->fid); if (!fidinfo) { - lib9p_errorf(&ctx->ctx.basectx, - LINUX_EBADF, "bad file number %"PRIu32, req->fid); - return; + lib9p_errorf(&ctx->basectx, + LIB9P_ERRNO_L_EBADF, "bad file number %"PRIu32, req->fid); + goto tread_return; } if (!(fidinfo->flags & FIDFLAG_OPEN_R)) { - lib9p_error(&ctx->ctx.basectx, - LINUX_EINVAL, "FID not open for reading"); - return; + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EINVAL, "FID not open for reading"); + goto tread_return; } - /* Variables. */ - struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path); - assert(pathinfo); - /* Do it. */ - if (srv_util_pathisdir(pathinfo)) { - /* Translate byte-offset to object-index. */ - size_t idx; - if (req->offset == 0) - idx = 0; - else if (req->offset == fidinfo->dir.off) - idx = fidinfo->dir.idx; - else { - lib9p_errorf(&ctx->ctx.basectx, - LINUX_EINVAL, "invalid offset (must be 0 or %"PRIu64"): %"PRIu64, + ctx->user = srv_userid_incref(fidinfo->user); + switch (fidinfo->type) { + case SRV_FILETYPE_DIR: +#if _LIB9P_ENABLE_stat + /* Seek. */ + if (req->offset == 0) { + fidinfo->dir.idx = 0; + fidinfo->dir.off = 0; + fidinfo->dir.buffered_dirent = (struct lib9p_srv_dirent){}; + } else if (req->offset != fidinfo->dir.off) { + lib9p_errorf(&ctx->basectx, + LIB9P_ERRNO_L_EINVAL, "invalid offset (must be 0 or %"PRIu64"): %"PRIu64, fidinfo->dir.off, req->offset); - return; + goto tread_return; } - /* Do it. */ - resp->data = (char *)(&resp[1]); - size_t num = LO_CALL(fidinfo->dir.io, dread, &ctx->ctx, (uint8_t *)resp->data, req->count, idx); - /* Translate object-count back to byte-count. */ - uint32_t len = 0; - for (size_t i = 0; i < num; i++) { - uint32_t i_len; - lib9p_stat_validate(&ctx->ctx.basectx, req->count, &((uint8_t *)resp->data)[len], &i_len, NULL); - len += i_len; + /* Read. */ + resp.data = heap = malloc(req->count); /* TODO: cap req->count */ + resp.count = 0; + struct srv_pathinfo *dir_pathinfo = NULL; + for (;;) { + lo_interface lib9p_srv_file member_file = {}; + struct lib9p_srv_dirent member_dirent; + if (fidinfo->dir.buffered_dirent.name.len) { + member_dirent = fidinfo->dir.buffered_dirent; + } else { + member_dirent = LO_CALL(fidinfo->dir.io, dread, ctx, fidinfo->dir.idx); + if (lib9p_ctx_has_error(&ctx->basectx)) { + if (!resp.count) + goto tread_return; + lib9p_ctx_clear_error(&ctx->basectx); + break; + } + } + if (!member_dirent.name.len) + break; + struct lib9p_srv_stat member_stat; + struct srv_pathinfo *member_pathinfo = map_load(&ctx->parent_sess->paths, member_dirent.qid.path); + if (member_pathinfo) { + member_stat = LO_CALL(member_pathinfo->file, stat, ctx); + } else { + if (!dir_pathinfo) + dir_pathinfo = map_load(&ctx->parent_sess->paths, fidinfo->path); + assert(dir_pathinfo); + member_file = LO_CALL(dir_pathinfo->file, dwalk, ctx, member_dirent.name); + assert(LO_IS_NULL(member_file) == lib9p_ctx_has_error(&ctx->basectx)); + if (!lib9p_ctx_has_error(&ctx->basectx)) + member_stat = LO_CALL(member_file, stat, ctx); + } + if (lib9p_ctx_has_error(&ctx->basectx)) { + if (!LO_IS_NULL(member_file)) + LO_CALL(member_file, free); + if (!resp.count) + goto tread_return; + lib9p_ctx_clear_error(&ctx->basectx); + break; + } + lib9p_srv_stat_assert(member_stat); + struct lib9p_stat member_netstat = srv_stat_to_net_stat(member_stat); + uint32_t nbytes = lib9p_stat_marshal(&ctx->basectx, req->count-resp.count, &member_netstat, + (uint8_t *)&resp.data[resp.count]); + if (!LO_IS_NULL(member_file)) + LO_CALL(member_file, free); + if (!nbytes) { + if (!resp.count) { + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_ERANGE, "stat object does not fit into negotiated max message size"); + goto tread_return; + } + fidinfo->dir.buffered_dirent = member_dirent; + break; + } + resp.count += nbytes; + fidinfo->dir.idx++; + fidinfo->dir.off += nbytes; + fidinfo->dir.buffered_dirent = (struct lib9p_srv_dirent){}; } - resp->count = len; - /* Remember. */ - fidinfo->dir.idx = idx+num; - fidinfo->dir.off = req->offset + len; - } else { +#else + assert_notreached("Tread for directory on protocol version without that"); +#endif + break; + case SRV_FILETYPE_FILE: struct iovec iov; - LO_CALL(fidinfo->file.io, pread, &ctx->ctx, req->count, req->offset, &iov); - if (!lib9p_ctx_has_error(&ctx->ctx.basectx)) { - resp->count = iov.iov_len; - resp->data = iov.iov_base; - if (resp->count > req->count) - resp->count = req->count; + LO_CALL(fidinfo->file.io, pread, ctx, req->count, req->offset, &iov); + if (!lib9p_ctx_has_error(&ctx->basectx) && !ctx->flush_acknowledged) { + resp.count = iov.iov_len; + resp.data = iov.iov_base; + if (resp.count > req->count) + resp.count = req->count; } + break; + case SRV_FILETYPE_AUTH: + assert_notreached("TODO: auth not yet implemented"); + break; } + tread_return: + if (ctx->user) + ctx->user = srv_userid_decref(ctx->user); + srv_respond(ctx, read, &resp); + if (heap) + free(heap); } -static void handle_Twrite(struct _lib9p_srv_req *ctx, - struct lib9p_msg_Twrite *req, - struct lib9p_msg_Rwrite *resp) { - util_handler_common(ctx, req, resp); +static void handle_Twrite(struct srv_req *ctx, + struct lib9p_msg_Twrite *req) { + srv_handler_common(ctx, write, req); /* TODO: serialize simultaneous writes to the same FID */ /* Check that the FID is valid for this. */ - struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, req->fid); + struct srv_fidinfo *fidinfo = map_load(&ctx->parent_sess->fids, req->fid); if (!fidinfo) { - lib9p_errorf(&ctx->ctx.basectx, - LINUX_EBADF, "bad file number %"PRIu32, req->fid); - return; + lib9p_errorf(&ctx->basectx, + LIB9P_ERRNO_L_EBADF, "bad file number %"PRIu32, req->fid); + goto twrite_return; } if (!(fidinfo->flags & FIDFLAG_OPEN_W)) { - lib9p_error(&ctx->ctx.basectx, - LINUX_EINVAL, "FID not open for writing"); - return; + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EINVAL, "FID not open for writing"); + goto twrite_return; } - - /* Variables. */ - struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path); - assert(pathinfo); + if (fidinfo->flags & FIDFLAG_APPEND) + req->offset = 0; /* Do it. */ - resp->count = LO_CALL(fidinfo->file.io, pwrite, &ctx->ctx, req->data, req->count, req->offset); + ctx->user = srv_userid_incref(fidinfo->user); + resp.count = LO_CALL(fidinfo->file.io, pwrite, ctx, req->data, req->count, req->offset); + twrite_return: + if (ctx->user) + ctx->user = srv_userid_decref(ctx->user); + srv_respond(ctx, write, &resp); } -static void clunkremove(struct _lib9p_srv_req *ctx, lib9p_fid_t fid, bool remove) { - struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, fid); +static void handle_Tclunk(struct srv_req *ctx, + struct lib9p_msg_Tclunk *req) { + srv_handler_common(ctx, clunk, req); + + struct srv_fidinfo *fidinfo = map_load(&ctx->parent_sess->fids, req->fid); if (!fidinfo) { - lib9p_errorf(&ctx->ctx.basectx, - LINUX_EBADF, "bad file number %"PRIu32, fid); - return; + lib9p_errorf(&ctx->basectx, + LIB9P_ERRNO_L_EBADF, "bad file number %"PRIu32, req->fid); + goto tclunk_return; } - if (fidinfo->flags & FIDFLAG_RCLOSE) - remove = true; - struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path); - assert(pathinfo); - if (remove) { - if (pathinfo->parent_dir == fidinfo->path) { - lib9p_errorf(&ctx->ctx.basectx, - LINUX_EBUSY, "cannot remove root"); - goto clunk; - } - struct srv_pathinfo *parent = pathmap_load(&ctx->parent_sess->paths, pathinfo->parent_dir); - assert(parent); - struct lib9p_stat parent_stat = LO_CALL(parent->file, stat, &ctx->ctx); - if (!srv_util_check_perm(ctx, &parent_stat, 0b010)) { - lib9p_error(&ctx->ctx.basectx, - LINUX_EACCES, "you do not have write permission on the parent directory"); - goto clunk; - } - LO_CALL(pathinfo->file, remove, &ctx->ctx); - } + srv_fid_del(ctx, req->fid, fidinfo, false); - clunk: - if (fidinfo->flags & FIDFLAG_OPEN) { - if (srv_util_pathisdir(pathinfo)) - LO_CALL(fidinfo->dir.io, iofree); - else - LO_CALL(fidinfo->file.io, iofree); - pathinfo->io_refcount--; - } - srv_util_pathfree(ctx, LO_CALL(pathinfo->file, qid).path); - fidmap_del(&ctx->parent_sess->fids, fid); + tclunk_return: + srv_respond(ctx, clunk, &resp); } -static void handle_Tclunk(struct _lib9p_srv_req *ctx, - struct lib9p_msg_Tclunk *req, - struct lib9p_msg_Rclunk *resp) { - util_handler_common(ctx, req, resp); - - clunkremove(ctx, req->fid, false); -} +static void handle_Tremove(struct srv_req *ctx, + struct lib9p_msg_Tremove *req) { + srv_handler_common(ctx, remove, req); + struct srv_fidinfo *fidinfo = map_load(&ctx->parent_sess->fids, req->fid); + if (!fidinfo) { + lib9p_errorf(&ctx->basectx, + LIB9P_ERRNO_L_EBADF, "bad file number %"PRIu32, req->fid); + goto tremove_return; + } -static void handle_Tremove(struct _lib9p_srv_req *ctx, - struct lib9p_msg_Tremove *req, - struct lib9p_msg_Rremove *resp) { - util_handler_common(ctx, req, resp); + bool remove = true; + struct srv_pathinfo *pathinfo = map_load(&ctx->parent_sess->paths, fidinfo->path); + assert(pathinfo); + if (pathinfo->parent_dir == fidinfo->path) { + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EBUSY, "cannot remove root"); + remove = false; + goto tremove_main; + } + struct srv_pathinfo *parent = map_load(&ctx->parent_sess->paths, pathinfo->parent_dir); + assert(parent); + struct lib9p_srv_stat parent_stat = LO_CALL(parent->file, stat, ctx); + if (!lib9p_ctx_has_error(&ctx->basectx) && !srv_check_perm(ctx, &parent_stat, 0b010)) { + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EACCES, "you do not have write permission on the parent directory"); + remove = false; + goto tremove_main; + } - clunkremove(ctx, req->fid, true); + tremove_main: + srv_fid_del(ctx, req->fid, fidinfo, remove); + tremove_return: + srv_respond(ctx, remove, &resp); } -static void handle_Tstat(struct _lib9p_srv_req *ctx, - struct lib9p_msg_Tstat *req, - struct lib9p_msg_Rstat *resp) { - util_handler_common(ctx, req, resp); +static void handle_Tstat(struct srv_req *ctx, + struct lib9p_msg_Tstat *req) { + srv_handler_common(ctx, stat, req); - struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, req->fid); + struct srv_fidinfo *fidinfo = map_load(&ctx->parent_sess->fids, req->fid); if (!fidinfo) { - lib9p_errorf(&ctx->ctx.basectx, - LINUX_EBADF, "bad file number %"PRIu32, req->fid); - return; + lib9p_errorf(&ctx->basectx, + LIB9P_ERRNO_L_EBADF, "bad file number %"PRIu32, req->fid); + goto tstat_return; } - struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path); + struct srv_pathinfo *pathinfo = map_load(&ctx->parent_sess->paths, fidinfo->path); assert(pathinfo); - resp->stat = LO_CALL(pathinfo->file, stat, &ctx->ctx); - if (!lib9p_ctx_has_error(&ctx->ctx.basectx)) - lib9p_stat_assert(resp->stat); + ctx->user = srv_userid_incref(fidinfo->user); + struct lib9p_srv_stat stat = LO_CALL(pathinfo->file, stat, ctx); + if (lib9p_ctx_has_error(&ctx->basectx)) + goto tstat_return; + lib9p_srv_stat_assert(stat); + resp.stat = srv_stat_to_net_stat(stat); + tstat_return: + if (ctx->user) + ctx->user = srv_userid_decref(ctx->user); + srv_respond(ctx, stat, &resp); } -static void handle_Twstat(struct _lib9p_srv_req *ctx, - struct lib9p_msg_Twstat *req, - struct lib9p_msg_Rwstat *resp) { - util_handler_common(ctx, req, resp); +static void handle_Twstat(struct srv_req *ctx, + struct lib9p_msg_Twstat *req) { + srv_handler_common(ctx, wstat, req); + + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EOPNOTSUPP, "wstat not (yet?) implemented"); - lib9p_error(&ctx->ctx.basectx, - LINUX_EOPNOTSUPP, "wstat not (yet?) implemented"); + srv_respond(ctx, wstat, &resp); } +#endif + +#if CONFIG_9P_ENABLE_9P2000_p9p +static void handle_Topenfd(struct srv_req *ctx, + struct lib9p_msg_Topenfd *req) { + srv_handler_common(ctx, openfd, req); + + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EOPNOTSUPP, "openfd not (yet?) implemented"); + + srv_respond(ctx, openfd, &resp); +} +#endif #if CONFIG_9P_ENABLE_9P2000_e -static void handle_Tsession(struct _lib9p_srv_req *ctx, - struct lib9p_msg_Tsession *req, - struct lib9p_msg_Rsession *resp) { - util_handler_common(ctx, req, resp); +static void handle_Tsession(struct srv_req *ctx, + struct lib9p_msg_Tsession *req) { + srv_handler_common(ctx, session, req); + + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EOPNOTSUPP, "session not (yet?) implemented"); - lib9p_error(&ctx->ctx.basectx, - LINUX_EOPNOTSUPP, "session not (yet?) implemented"); + srv_respond(ctx, session, &resp); } -static void handle_Tsread(struct _lib9p_srv_req *ctx, - struct lib9p_msg_Tsread *req, - struct lib9p_msg_Rsread *resp) { - util_handler_common(ctx, req, resp); +static void handle_Tsread(struct srv_req *ctx, + struct lib9p_msg_Tsread *req) { + srv_handler_common(ctx, sread, req); - lib9p_error(&ctx->ctx.basectx, - LINUX_EOPNOTSUPP, "sread not (yet?) implemented"); + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EOPNOTSUPP, "sread not (yet?) implemented"); + + srv_respond(ctx, sread, &resp); } -static void handle_Tswrite(struct _lib9p_srv_req *ctx, - struct lib9p_msg_Tswrite *req, - struct lib9p_msg_Rswrite *resp) { - util_handler_common(ctx, req, resp); +static void handle_Tswrite(struct srv_req *ctx, + struct lib9p_msg_Tswrite *req) { + srv_handler_common(ctx, swrite, req); + + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EOPNOTSUPP, "swrite not (yet?) implemented"); - lib9p_error(&ctx->ctx.basectx, - LINUX_EOPNOTSUPP, "swrite not (yet?) implemented"); + srv_respond(ctx, swrite, &resp); } #endif |