summaryrefslogtreecommitdiff
path: root/lib9p/srv.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib9p/srv.c')
-rw-r--r--lib9p/srv.c1918
1 files changed, 1214 insertions, 704 deletions
diff --git a/lib9p/srv.c b/lib9p/srv.c
index 2475baf..3e4de91 100644
--- a/lib9p/srv.c
+++ b/lib9p/srv.c
@@ -4,15 +4,16 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-#include <alloca.h>
-#include <inttypes.h> /* for PRI* */
+#include <stddef.h> /* for size_t */
+#include <string.h> /* for memcpy() */
#include <libcr/coroutine.h>
-#include <libcr_ipc/chan.h>
#include <libcr_ipc/mutex.h>
-#include <libcr_ipc/select.h>
-#include <libmisc/assert.h>
#include <libhw/generic/net.h>
+#include <libmisc/alloc.h>
+#include <libmisc/assert.h>
+#include <libmisc/endian.h>
+#include <libmisc/map.h>
#define LOG_NAME 9P_SRV
#include <libmisc/log.h>
@@ -20,334 +21,607 @@
#define IMPLEMENTATION_FOR_LIB9P_SRV_H YES
#include <lib9p/srv.h>
-#include "internal.h"
+#include "srv_errno.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
+/* config *********************************************************************/
+
+#include "config.h"
+
+#ifndef CONFIG_9P_SRV_MAX_MSG_SIZE
+ #error config.h must define CONFIG_9P_SRV_MAX_MSG_SIZE
#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
+#ifndef CONFIG_9P_SRV_MAX_HOSTMSG_SIZE
+ #error config.h must define CONFIG_9P_SRV_MAX_HOSTMSG_SIZE
#endif
+static_assert(CONFIG_9P_SRV_MAX_MSG_SIZE <= CONFIG_9P_SRV_MAX_HOSTMSG_SIZE);
+static_assert(CONFIG_9P_SRV_MAX_HOSTMSG_SIZE <= SIZE_MAX);
/* context ********************************************************************/
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) {
- 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;
-}
+#define req_debug(...) \
+ log_debugln( \
+ "cid=", cr_getcid(), ": ", \
+ lib9p_msgtype_str(ctx->basectx.version, ctx->net_bytes[4]), "(tag=", ctx->tag, "): ", \
+ __VA_ARGS__)
/* structs ********************************************************************/
+#ifndef NDEBUG
+void lib9p_srv_stat_assert(const struct lib9p_srv_stat *stat) {
+ assert(stat);
+ assert( ((bool)(stat->mode & LIB9P_DM_DIR )) == ((bool)(stat->qid.type & LIB9P_QT_DIR )) );
+ assert( ((bool)(stat->mode & LIB9P_DM_APPEND)) == ((bool)(stat->qid.type & LIB9P_QT_APPEND)) );
+ assert( ((bool)(stat->mode & LIB9P_DM_EXCL )) == ((bool)(stat->qid.type & LIB9P_QT_EXCL )) );
+ assert( ((bool)(stat->mode & LIB9P_DM_AUTH )) == ((bool)(stat->qid.type & LIB9P_QT_AUTH )) );
+ assert( ((bool)(stat->mode & LIB9P_DM_TMP )) == ((bool)(stat->qid.type & LIB9P_QT_TMP )) );
+ assert( (stat->size == 0) || !(stat->mode & LIB9P_DM_DIR) );
+}
+#endif
+
+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
* from FIDs. */
unsigned int gc_refcount;
/* References from fids with FIDFLAG_OPEN_R/FIDFLAG_OPEN_W. */
- unsigned int rd_refcount;
- unsigned int wr_refcount;
+ 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 {
- srv_path_t path;
- uint8_t flags;
- uint32_t iounit;
- size_t dir_idx;
- uint64_t dir_off;
+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;
+ } file;
+ struct {
+ 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;
+ };
};
+DECLARE_ERROR_OR_(struct srv_fidinfo *, srv_fidinfop);
-#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;
- cid_t reader; /* the lib9p_srv_read_cr() coroutine */
+ cid_t reader; /* the lib9p_srv_read() coroutine */
/* mutable */
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;
- struct reqmap reqs;
- struct fidmap fids;
+ 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 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;
+}
+
+[[maybe_unused]]
+static bool srv_stat_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;
+}
+
+[[maybe_unused]]
+static ok_or_error srv_file_check_perm(struct srv_req *ctx, lo_interface lib9p_srv_file file, uint8_t action) {
+ assert(ctx);
+ assert(!LO_IS_NULL(file));
+ assert(action);
+
+ struct lib9p_srv_stat stat;
+ error err = LO_CALL(file, stat, ctx, &stat);
+ if (!ERROR_IS_NULL(err))
+ return ERROR_NEW_ERR(ok, err);
+ lib9p_srv_stat_assert(&stat);
+ return ERROR_NEW_VAL(ok, srv_stat_check_perm(ctx, &stat, action));
+}
+
+[[maybe_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 = (void *)heap_alloc(sizeof(struct lib9p_srv_userid) + name.len, char);
+ void *end_dat = (void *)&ret[1];
+ memcpy(end_dat, name.utf8, name.len);
+#if CONFIG_9P_ENABLE_9P2000_u || CONFIG_9P_ENABLE_9P2000_L
+ ret->num = num;
+#endif
+ ret->name.len = name.len;
+ ret->name.utf8 = end_dat;
+ 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.
+ */
+[[maybe_unused]]
+static 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 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 error srv_fid_del(struct srv_req *ctx, lib9p_fid_t fid, struct srv_fidinfo *fidinfo, bool remove) {
+ assert(ctx);
+ assert(!ctx->user);
+ assert(fidinfo);
+
+ error err = {};
+
+ 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)
+ err = 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);
+
+ return err;
+}
+
+/**
+ * Store fid as pointing to pathinfo. Assumes that
+ * pathinfo->gc_refcount has already been incremented; does *not*
+ * decrement it on failure.
+ */
+[[maybe_unused]]
+static srv_fidinfop_or_error 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 {
+ return ERROR_NEW_ERR(srv_fidinfop, error_new(E_POSIX_EBADF, "FID already in use"));
+ }
+ }
+ 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 ERROR_NEW_VAL(srv_fidinfop, 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;
+ }
+ log_infoln(typ % 2 ? "< " : "> ", (lib9p_msg, &req->basectx, typ, hostmsg));
+}
+
+#define srv_nonrespond_error log_errorln
+
+static error srv_write_Rmsg(struct srv_req *req, enum lib9p_msg_type typ, void *host) {
+ assert(req);
+ assert(typ % 2 == 1);
+ assert(host);
+
+ struct lib9p_Rmsg_send_buf net;
+ error err = lib9p_Rmsg_marshal(&req->basectx,
+ typ, host,
+ &net);
+ if (!ERROR_IS_NULL(err))
+ return err;
+
+ srv_msglog(req, typ, host);
-static ssize_t write_Rmsg(struct _lib9p_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);
+ size_t_and_error r = io_writev(req->parent_sess->parent_conn->fd, net.iov, net.iov_cnt);
cr_mutex_unlock(&req->parent_sess->parent_conn->writelock);
- return r;
+ if (!ERROR_IS_NULL(r.err)) {
+ srv_nonrespond_error("write: (", r.size_t, ", ", (error, r.err), ")");
+ error_cleanup(&r.err);
+ }
+ return ERROR_NULL;
}
-static void respond_error(struct _lib9p_srv_req *req) {
-#if CONFIG_9P_ENABLE_9P2000_u
- assert(req->ctx.basectx.err_num);
-#endif
- assert(req->ctx.basectx.err_msg[0]);
+static void srv_respond_error(struct srv_req *req, error err) {
+ assert(!ERROR_IS_NULL(err));
- 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_str(error_msg(err)),
#if CONFIG_9P_ENABLE_9P2000_u
- .errno = req->ctx.basectx.err_num,
+ .errnum = libmisc_to_linuxgeneric_errno(err.num),
#endif
};
- struct _srv_sess *sess = req->parent_sess;
+ struct srv_sess *sess = req->parent_sess;
- /* 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;
+ uint32_t overhead = lib9p_version_min_Rerror_size(sess->version);
- struct lib9p_Rmsg_send_buf net;
+ /* Truncate the error-string if necessary to avoid needing to
+ * return E_POSIX_ERANGE. */
+ if (((uint32_t)host.errstr.len) + overhead > sess->max_msg_size)
+ host.errstr.len = sess->max_msg_size - overhead;
- lib9p_Rmsg_marshal(&req->ctx.basectx,
- LIB9P_TYP_Rerror, &host,
- &net);
+ error marshal_err = srv_write_Rmsg(req, LIB9P_TYP_Rerror, &host);
+ assert(ERROR_IS_NULL(marshal_err));
- r = write_Rmsg(req, &net);
- if (r < 0)
- nonrespond_errorf("write: %s", net_strerror(-r));
+ error_cleanup(&err);
}
/* read coroutine *************************************************************/
-static bool read_exactly(lo_interface net_stream_conn fd, uint8_t *buf, size_t goal, size_t *done) {
+/** Return whether to `break`. */
+static 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));
- return true;
- } else if (r == 0) {
- if (*done != 0)
- nonrespond_errorf("read: unexpected EOF");
+ size_t_or_error r = io_read(fd, &buf[*done], goal - *done);
+ if (r.is_err) {
+ if (r.err.num == E_EOF) {
+ if (*done != 0)
+ srv_nonrespond_error("read: unexpected EOF");
+ } else {
+ srv_nonrespond_error("read: ", (error, r.err));
+ }
+ error_cleanup(&r.err);
return true;
}
- *done += r;
+ *done += r.size_t;
}
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_table_msg_min_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");
+ net_stream_conn_or_error r = LO_CALL(listener, accept);
+ if (r.is_err) {
+ srv_nonrespond_error("accept: ", (error, r.err));
+ error_cleanup(&r.err);
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, r.net_stream_conn);
+ }
+}
- struct _srv_sess sess = {
- .parent_conn = &conn,
- .version = LIB9P_VER_unknown,
- .max_msg_size = CONFIG_9P_MAX_MSG_SIZE,
- .rerror_overhead = initial_rerror_overhead,
- .initialized = false,
- };
- for (;;) {
- nextmsg:
- /* Read the message. */
- size_t done = 0;
- 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;
- }
+static void lib9p_srv_worker_Tversion(struct srv_req *ctx);
- /* 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);
+void lib9p_srv_read(struct lib9p_srv *srv, lo_interface net_stream_conn _conn) {
+ assert(srv);
+ assert(srv->rootdir);
+ assert(!LO_IS_NULL(_conn));
+
+ struct srv_conn _conn_ = {
+ .parent_srv = srv,
+ .fd = _conn,
+ .reader = cr_getcid(),
+ };
+ struct srv_conn *conn = &_conn_;
+ struct srv_sess _sess_ = {
+ .parent_conn = conn,
+ .version = LIB9P_VER_uninitialized,
+ .max_msg_size = CONFIG_9P_SRV_MAX_MSG_SIZE,
+ };
+ struct srv_sess *sess = &_sess_;
+ struct srv_req _req_;
+ struct srv_req *req = &_req_;
+
+ 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_error("T-message is impossibly small");
+ break;
+ }
+ if (srv_read_exactly(conn->fd, buf, 7, &done))
+ break;
+ *req = (struct srv_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) {
+ srv_respond_error(req, error_new(E_POSIX_EMSGSIZE,
+ "T-message larger than ", sess->version == LIB9P_VER_uninitialized ? "server" : "negotiated", " limit",
+ " (", goal, " > ", sess->max_msg_size, ")"));
+ 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 = heap_alloc(goal, uint8_t);
+ 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_Tversion(req);
+ else
+ /* ...but usually in another coroutine. */
+ cr_rpc_send_req(&srv->reqch, req);
+ }
+
+ /* Cleanup `conn` and `sess->reqs`. */
+ error err = io_close_read(conn->fd);
+ if (!ERROR_IS_NULL(err)) {
+ srv_nonrespond_error("conn close: ", (error, err));
+ error_cleanup(&err);
}
+ if (map_len(&sess->reqs)) {
+ sess->closing = true;
+ cr_pause_and_yield();
+ }
+ assert(map_len(&sess->reqs) == 0);
+ err = io_close_write(conn->fd);
+ if (!ERROR_IS_NULL(err)) {
+ srv_nonrespond_error("conn close: ", (error, err));
+ error_cleanup(&err);
+ }
+ map_free(&sess->reqs);
+
+ /* Cleanup `sess->fids`. */
+ *req = (struct srv_req){
+ .basectx = {
+ .version = sess->version,
+ .max_msg_size = sess->max_msg_size,
+ },
+ .parent_sess = sess,
+ };
+ MAP_FOREACH(&sess->fids, fid, fidinfo) {
+ err = srv_fid_del(req, fid, fidinfo, false);
+ if (!ERROR_IS_NULL(err)) {
+ srv_nonrespond_error("clunk: ", (error, err));
+ error_cleanup(&err);
+ }
+ }
+ assert(map_len(&sess->fids) == 0);
+ map_free(&sess->fids);
+
+ /* Cleanup `sess->paths`. */
+ 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);
@@ -360,188 +634,149 @@ _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
-};
-
-static void handle_message(struct _lib9p_srv_req *ctx) {
- uint8_t *host_req = NULL;
- uint8_t host_resp[CONFIG_9P_MAX_HOSTMSG_SIZE];
+void lib9p_srv_worker(struct srv_req *ctx) {
+ [[gnu::cleanup(heap_cleanup)]] void *host_req = NULL;
- /* Unmarshal it. */
- ssize_t host_size = lib9p_Tmsg_validate(&ctx->ctx.basectx, ctx->net_bytes);
- if (host_size < 0)
- goto write;
- host_req = malloc(host_size);
- assert(host_req);
+ /* Unmarshal it. *****************************************************/
+ size_t_or_error r = lib9p_Tmsg_validate(&ctx->basectx, ctx->net_bytes);
+ if (r.is_err) {
+ srv_respond_error(ctx, r.err);
+ goto release;
+ }
+ size_t host_size = r.size_t;
+ host_req = heap_alloc(host_size, char);
enum lib9p_msg_type typ;
- lib9p_Tmsg_unmarshal(&ctx->ctx.basectx, ctx->net_bytes,
- &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;
- write_Rmsg(ctx, &net_resp);
+ lib9p_Tmsg_unmarshal(&ctx->basectx, ctx->net_bytes,
+ &typ, host_req);
+ srv_msglog(ctx, typ, host_req);
+
+ /* Handle it. ********************************************************/
+#define CASE(typ) case LIB9P_TYP_##typ: handle_##typ(ctx, (void *)host_req); break
+ LM_PARTIAL_SWITCH (typ) {
+#if _LIB9P_ENABLE_stat
+ CASE(Tauth);
+ CASE(Tattach);
+ CASE(Tflush);
+ CASE(Twalk);
+ CASE(Topen);
+ CASE(Tcreate);
+ CASE(Tread);
+ CASE(Twrite);
+ CASE(Tclunk);
+ CASE(Tremove);
+ CASE(Tstat);
+ CASE(Twstat);
+#endif
+#if CONFIG_9P_ENABLE_9P2000_p9p
+ CASE(Topenfd);
+#endif
+#if CONFIG_9P_ENABLE_9P2000_e
+ CASE(Tsession);
+ CASE(Tsread);
+ CASE(Tswrite);
+#endif
+#undef CASE
+ default:
+ assert_notreached("lib9p_Tmsg_validate() should have rejected unknown typ");
+ }
+ 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 (host_req)
- free(host_req);
+ if (ctx->parent_sess->closing && !map_len(&ctx->parent_sess->reqs))
+ cr_unpause(ctx->parent_sess->parent_conn->reader);
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;
-}
+static void lib9p_srv_worker_Tversion(struct srv_req *ctx) {
+ [[gnu::cleanup(heap_cleanup)]] void *host_req = NULL;
-/**
- * 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,
- .rd_refcount = 0,
- .wr_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++;
- }
+ /* Unmarshal it. *****************************************************/
+ size_t_or_error r = lib9p_Tmsg_validate(&ctx->basectx, ctx->net_bytes);
+ if (r.is_err) {
+ srv_respond_error(ctx, r.err);
+ goto release;
}
- 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);
+ size_t host_size = r.size_t;
+ host_req = heap_alloc(host_size, char);
+ enum lib9p_msg_type typ;
+ lib9p_Tmsg_unmarshal(&ctx->basectx, ctx->net_bytes,
+ &typ, host_req);
+ srv_msglog(ctx, typ, host_req);
+
+ /* Handle it. ********************************************************/
+ handle_Tversion(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);
+ free(ctx->net_bytes);
}
-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) {
- srv_util_pathfree(ctx, fidinfo->path);
+static void _srv_respond(struct srv_req *ctx, enum lib9p_msg_type resp_typ, void *host_resp, error err) {
+ assert(!ctx->responded);
+ if (!ERROR_IS_NULL(err)) {
+ if (err.num == E_POSIX_ECANCELED && lib9p_srv_flush_requested(ctx)) {
+ error_cleanup(&err);
} else {
- lib9p_error(&ctx->ctx.basectx,
- LINUX_EBADF, "FID already in use");
- return NULL;
+ error:
+ srv_respond_error(ctx, err);
}
} 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);
+ err = srv_write_Rmsg(ctx, resp_typ, host_resp);
+ if (!ERROR_IS_NULL(err))
+ goto error;
}
- *fidinfo = (struct _srv_fidinfo){
- .path = qid.path,
- };
- return fidinfo;
+ ctx->responded = true;
}
+#define srv_respond(CTX, TYP, HOST_RESP, ERR) do { \
+ struct lib9p_msg_R##TYP *_host_resp = HOST_RESP; \
+ _srv_respond(CTX, LIB9P_TYP_R##TYP, _host_resp, ERR); \
+} while (0)
+
+/* handle_T* ******************************************************************/
+#define srv_handler_common_no_err(ctx, typ, req) \
+ assert(ctx); \
+ assert(req); \
+ struct lib9p_msg_T##typ *_typecheck_req [[maybe_unused]] = req; \
+ struct lib9p_msg_R##typ resp = { .tag = ctx->tag }
+#define srv_handler_common(ctx, typ, req) \
+ srv_handler_common_no_err(ctx, typ, req); \
+ error err = {}
-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;
+ enum lib9p_version version = LIB9P_VER_uninitialized;
if (req->version.len >= 6 &&
req->version.utf8[0] == '9' &&
@@ -551,7 +786,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;
@@ -562,530 +804,798 @@ static void handle_Tversion(struct _lib9p_srv_req *ctx,
#endif
}
- uint32_t min_msg_size = _lib9p_table_msg_min_size[version];
+ /* XXX: There are good arguments that min_msg_size should be
+ * something larger than max(rerror.min_size(),
+ * rread.min_size()+1). */
+ uint32_t min_msg_size = _LIB9P_MAX(lib9p_version_min_Rerror_size(ctx->basectx.version),
+ lib9p_version_min_Rread_size(ctx->basectx.version)+1);
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_version_str(version), req->max_msg_size, min_msg_size);
- return;
+ err = error_new(E_POSIX_EDOM, "requested max_msg_size is less than minimum for ", lib9p_version_str(version),
+ " (", req->max_msg_size, " < ", min_msg_size, ")");
+ goto tversion_return;
}
- resp->version = lib9p_str((char *)lib9p_version_str(version)); /* cast to discard "const" qualifier */
- resp->max_msg_size = (CONFIG_9P_MAX_MSG_SIZE < req->max_msg_size)
- ? CONFIG_9P_MAX_MSG_SIZE
+ resp.version = lib9p_str(lib9p_version_str(version));
+#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 = heap_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);
}
+ free(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) {
+ error fiderr = srv_fid_del(ctx, fid, fidinfo, false);
+ if (!ERROR_IS_NULL(fiderr)) {
+ srv_nonrespond_error("clunk: ", (error, fiderr));
+ error_cleanup(&fiderr);
}
}
/* 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, err);
}
-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;
+ err = error_new(E_POSIX_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);
+
+ err = srv->auth(ctx, req->aname);
+ if (!ERROR_IS_NULL(err))
+ goto tauth_return;
+
+ err = error_new(E_POSIX_EOPNOTSUPP, "TODO: auth not implemented");
+
+ tauth_return:
+ if (!ERROR_IS_NULL(err) && ctx->user)
+ ctx->user = srv_userid_decref(ctx->user);
+ srv_respond(ctx, auth, &resp, err);
}
-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) {
+ err = error_new(E_POSIX_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,
- 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,
- 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;
+ struct srv_fidinfo *afid = map_load(&ctx->parent_sess->fids, req->afid);
+ if (!afid)
+ err = error_new(E_POSIX_EACCES, "FID provided as auth-file is not a valid FID");
+ else if (afid->type != SRV_FILETYPE_AUTH)
+ err = error_new(E_POSIX_EACCES, "FID provided as auth-file is not an auth-file");
+ else if (!lib9p_str_eq(afid->user->name, req->uname))
+ err = error_new(E_POSIX_EACCES,
+ "FID provided as auth-file is for user=", (qmem, afid->user->name.utf8, afid->user->name.len),
+ " and cannot be used for user=", (qmem, req->uname.utf8, req->uname.len));
+#if CONFIG_9P_ENABLE_9P2000_u || CONFIG_9P_ENABLE_9P2000_L
+ else if (afid->user->num != req->unum)
+ err = error_new(E_POSIX_EACCES,
+ "FID provided as auth-file is for user=", afid->user->num,
+ " and cannot be used for user=", req->unum);
+#endif
+ else if (!lib9p_str_eq(afid->auth.aname, req->aname))
+ err = error_new(E_POSIX_EACCES,
+ "FID provided as auth-file is for tree=", (qmem, afid->auth.aname.utf8, afid->auth.aname.len),
+ " and cannot be used for tree=", (qmem, req->aname.utf8, req->aname.len));
+ else if (!afid->auth.completed)
+ err = error_new(E_POSIX_EACCES, "FID provided as auth-file has not completed authentication");
+ if (!ERROR_IS_NULL(err))
+ 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;
+ err = error_new(E_POSIX_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;
+ lib9p_srv_file_or_error root_file_r = srv->rootdir(ctx, req->aname);
+ if (root_file_r.is_err) {
+ err = root_file_r.err;
+ goto tattach_return;
+ }
+ lo_interface lib9p_srv_file root_file = root_file_r.lib9p_srv_file;
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;
+ srv_fidinfop_or_error fidinfo = srv_fid_store(ctx, req->fid, root_pathinfo, false);
+ if (fidinfo.is_err) {
+ err = fidinfo.err;
+ 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, err);
}
-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_debug("original request (tag=", req->oldtag, ") returned");
+ if (res == _LIB9P_SRV_FLUSH_SILENT) {
+ ctx->responded = true;
+ return;
+ }
+ break;
+ case 1: /* flush itself got flushed */
+ ctx->responded = true;
+ return;
+ }
+ }
+ srv_respond(ctx, flush, &resp, err);
}
-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;
+ err = error_new(E_POSIX_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;
+ err = error_new(E_POSIX_EBADF, "bad file number ", req->fid);
+ goto twalk_return;
}
+ if (fidinfo->flags & FIDFLAG_OPEN) {
+ err = error_new(E_POSIX_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++) {
+ resp.wqid = heap_alloc(req->nwname, struct lib9p_qid);
+ for (resp.nwqid = 0; resp.nwqid < req->nwname; resp.nwqid++) {
+ if (pathinfo->type != SRV_FILETYPE_DIR) {
+ err = error_new(E_POSIX_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");
+ lib9p_srv_file_or_error member_file = LO_CALL(pathinfo->file, dwalk, ctx, req->wname[resp.nwqid]);
+ if (member_file.is_err) {
+ err = member_file.err;
break;
}
-
- lo_interface lib9p_srv_file member_file = LO_CALL(pathinfo->file, dopen, &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))
- break;
- new_pathinfo = srv_util_pathsave(ctx, member_file, LO_CALL(pathinfo->file, qid).path);
+ new_pathinfo = srv_path_save(ctx, member_file.lib9p_srv_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) {
+ ok_or_error have_perm = srv_file_check_perm(ctx, new_pathinfo->file, 0b001);
+ if (have_perm.is_err) {
+ err = have_perm.err;
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);
+ } else if (!have_perm.ok) {
+ err = error_new(E_POSIX_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 (!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) {
+ srv_fidinfop_or_error fidinfo = srv_fid_store(ctx, req->newfid, pathinfo, req->newfid == req->fid);
+ if (fidinfo.is_err) {
+ err = fidinfo.err;
+ 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(!ERROR_IS_NULL(err));
+ srv_path_decref(ctx, LO_CALL(pathinfo->file, qid).path);
+ if (resp.nwqid > 0)
+ error_cleanup(&err);
}
+ twalk_return:
+ if (ctx->user)
+ ctx->user = srv_userid_decref(ctx->user);
+ srv_respond(ctx, walk, &resp, err);
+ if (resp.wqid)
+ free(resp.wqid);
}
-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;
+ err = error_new(E_POSIX_EBADF, "bad file number ", req->fid);
+ goto topen_return;
}
if (fidinfo->flags & FIDFLAG_OPEN) {
- lib9p_error(&ctx->ctx.basectx,
- LINUX_EALREADY, "FID is already open");
- return;
+ err = error_new(E_POSIX_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 ( ((req->mode & LIB9P_O_MODE_MASK) != LIB9P_O_READ) ||
+ 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;
+ err = error_new(E_POSIX_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;
- uint32_t iounit = fidinfo->iounit;
+ 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;
+ ok_or_error have_perm = srv_file_check_perm(ctx, parent->file, 0b010);
+ if (have_perm.is_err) {
+ err = have_perm.err;
+ goto topen_return;
+ } else if (!have_perm.ok) {
+ err = error_new(E_POSIX_EACCES, "permission denied to remove-on-close");
+ goto topen_return;
}
- fidflags = fidflags | FIDFLAG_RCLOSE;
+ 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->rd_refcount || pathinfo->wr_refcount)) {
- lib9p_error(&ctx->ctx.basectx,
- LINUX_EEXIST, "exclusive file is already opened");
- return;
+ struct lib9p_srv_stat stat;
+ err = LO_CALL(pathinfo->file, stat, ctx, &stat);
+ if (!ERROR_IS_NULL(err))
+ goto topen_return;
+ lib9p_srv_stat_assert(&stat);
+ if ((stat.mode & LIB9P_DM_EXCL) && pathinfo->io_refcount) {
+ err = error_new(E_POSIX_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) {
- case LIB9P_O_READ:
+ case LIB9P_O_MODE_READ:
perm_bits = 0b100;
rd = true;
break;
- case LIB9P_O_WRITE:
+ case LIB9P_O_MODE_WRITE:
perm_bits = 0b010;
wr = true;
break;
- case LIB9P_O_RDWR:
+ case LIB9P_O_MODE_RDWR:
perm_bits = 0b110;
rd = wr = true;
break;
- case LIB9P_O_EXEC:
+ case LIB9P_O_MODE_EXEC:
perm_bits = 0b001;
rd = true;
break;
}
- if (!srv_util_check_perm(ctx, &stat, perm_bits)) {
- lib9p_error(&ctx->ctx.basectx,
- LINUX_EACCES, "permission denied");
+ if (!srv_stat_check_perm(ctx, &stat, perm_bits)) {
+ err = error_new(E_POSIX_EACCES, "permission denied");
+ goto topen_return;
}
/* Actually make the call. */
- if ((reqmode & LIB9P_O_TRUNC) || (rd && !pathinfo->rd_refcount) || (wr && !pathinfo->wr_refcount) ) {
- iounit = LO_CALL(pathinfo->file, chio, &ctx->ctx,
- fidflags & FIDFLAG_OPEN_R,
- fidflags & FIDFLAG_OPEN_W,
- reqmode & LIB9P_O_TRUNC);
- if (lib9p_ctx_has_error(&ctx->ctx.basectx))
- return;
+ uint32_t iounit;
+ struct lib9p_qid qid;
+ switch (pathinfo->type) {
+ case SRV_FILETYPE_DIR:
+ lib9p_srv_dio_or_error dio_r =
+ LO_CALL(pathinfo->file, dopen, ctx);
+ if (dio_r.is_err) {
+ err = dio_r.err;
+ goto topen_return;
+ }
+ fidinfo->dir.io = dio_r.lib9p_srv_dio;
+ fidinfo->dir.idx = 0;
+ fidinfo->dir.off = 0;
+ qid = LO_CALL(fidinfo->dir.io, ioqid);
+ iounit = 0;
+ break;
+ case SRV_FILETYPE_FILE:
+ lib9p_srv_fio_or_error fio_r =
+ LO_CALL(pathinfo->file, fopen, ctx,
+ rd, wr,
+ reqmode & LIB9P_O_TRUNC);
+ if (fio_r.is_err) {
+ err = fio_r.err;
+ goto topen_return;
+ }
+ fidinfo->file.io = fio_r.lib9p_srv_fio;
+ qid = LO_CALL(fidinfo->file.io, ioqid);
+ 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. */
- if (rd) {
- fidflags = fidflags | FIDFLAG_OPEN_R;
- pathinfo->rd_refcount++;
- }
- if (wr) {
- fidflags = fidflags | FIDFLAG_OPEN_W;
- pathinfo->wr_refcount++;
- }
+ if (rd)
+ fidflags |= FIDFLAG_OPEN_R;
+ if (wr)
+ fidflags |= FIDFLAG_OPEN_W;
+ pathinfo->io_refcount++;
fidinfo->flags = fidflags;
- resp->qid = stat.file_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, err);
}
-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);
+
+ err = error_new(E_POSIX_EOPNOTSUPP, "create not (yet?) implemented");
- lib9p_error(&ctx->ctx.basectx,
- LINUX_EOPNOTSUPP, "create not (yet?) implemented");
+ srv_respond(ctx, create, &resp, err);
}
-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 void srv_stat_to_net_stat(struct lib9p_stat *out, const struct lib9p_srv_stat *in) {
+ lib9p_srv_stat_assert(in);
+ *out = (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_read_file(struct srv_req *ctx, struct srv_fidinfo *fidinfo, uint64_t offset, uint32_t count);
+static void handle_read_dir(struct srv_req *ctx, struct srv_fidinfo *fidinfo, uint64_t offset, uint32_t count);
+
+static void handle_Tread(struct srv_req *ctx,
+ struct lib9p_msg_Tread *req) {
+ assert(ctx);
+ assert(req);
+
+ /* TODO: serialize simultaneous reads to the same FID */
+
+ /* req->count <= CONFIG_9P_SRV_MAX_MSG_SIZE <= CONFIG_9P_SRV_MAX_HOSTMSG_SIZE <= SIZE_MAX */
+ assert(req->count <= SIZE_MAX);
+ /* req->offset is u64, uoff is u64 */
+ static_assert(req->offset <= UOFF_MAX);
+
+ if (req->count > ctx->basectx.max_msg_size - lib9p_version_min_Rread_size(ctx->basectx.version))
+ req->count = ctx->basectx.max_msg_size - lib9p_version_min_Rread_size(ctx->basectx.version);
/* 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);
+ srv_respond(ctx, read, NULL, error_new(E_POSIX_EBADF, "bad file number ", req->fid));
return;
}
if (!(fidinfo->flags & FIDFLAG_OPEN_R)) {
- lib9p_error(&ctx->ctx.basectx,
- LINUX_EINVAL, "FID not open for reading");
+ srv_respond(ctx, read, NULL, error_new(E_POSIX_EINVAL, "FID not open for reading"));
return;
}
- /* Variables. */
- struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path);
- assert(pathinfo);
- resp->data = (char *)(&resp[1]);
-
/* 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,
- fidinfo->dir_off, req->offset);
+ ctx->user = srv_userid_incref(fidinfo->user);
+ switch (fidinfo->type) {
+ case SRV_FILETYPE_DIR:
+#if _LIB9P_ENABLE_stat
+ handle_read_dir(ctx, fidinfo, req->offset, req->count);
+#else
+ assert_notreached("Tread for directory on protocol version without that");
+#endif
+ break;
+ case SRV_FILETYPE_FILE:
+ handle_read_file(ctx, fidinfo, req->offset, req->count);
+ break;
+ case SRV_FILETYPE_AUTH:
+ assert_notreached("TODO: auth not yet implemented");
+ break;
+ }
+ ctx->user = srv_userid_decref(ctx->user);
+}
+
+struct rread_writer {
+ struct srv_req *ctx;
+ size_t count;
+ bool written;
+
+};
+LO_IMPLEMENTATION_STATIC(io_writer, struct rread_writer, rread);
+
+static size_t_and_error rread_writev(struct rread_writer *self, const struct wr_iovec *iov, int iovcnt) {
+ assert(self);
+ assert(!self->written);
+ assert(iovcnt == 1);
+ assert(iov);
+ assert(iov->iov_len <= self->count);
+
+ struct lib9p_msg_Rread resp = {
+ .tag = self->ctx->tag,
+ .count = iov->iov_len,
+ .data = iov->iov_write_from,
+ };
+
+ srv_respond(self->ctx, read, &resp, ERROR_NULL);
+
+ self->written = true;
+ return ERROR_AND(size_t, iov->iov_len, ERROR_NULL);
+}
+
+static void handle_read_file(struct srv_req *ctx, struct srv_fidinfo *fidinfo, uint64_t offset, uint32_t count) {
+ struct rread_writer _writer = {
+ .ctx = ctx,
+ .count = (size_t) count,
+ .written = false,
+ };
+ lo_interface io_writer writer = LO_BOX(io_writer, &_writer);
+ error err = LO_CALL(fidinfo->file.io, pread, ctx, writer, offset, count);
+ assert(ERROR_IS_NULL(err) == _writer.written);
+ if (!ERROR_IS_NULL(err))
+ srv_respond(ctx, read, NULL, err);
+}
+
+#if _LIB9P_ENABLE_stat
+
+static error stat_and_convert(struct lib9p_stat *out, struct srv_req *ctx, lo_interface lib9p_srv_file file) {
+ struct lib9p_srv_stat hoststat;
+ error err = LO_CALL(file, stat, ctx, &hoststat);
+ if (!ERROR_IS_NULL(err))
+ return err;
+
+ srv_stat_to_net_stat(out, &hoststat);
+ return ERROR_NULL;
+}
+
+static uint32_t_or_error stat_and_encode(struct srv_req *ctx, lo_interface lib9p_srv_file file, void *out, size_t out_len) {
+ struct lib9p_srv_stat hoststat;
+ struct lib9p_stat netstat;
+ error err;
+
+ err = LO_CALL(file, stat, ctx, &hoststat);
+
+ if (!ERROR_IS_NULL(err))
+ return ERROR_NEW_ERR(uint32_t, err);
+ srv_stat_to_net_stat(&netstat, &hoststat);
+ return ERROR_NEW_VAL(uint32_t, lib9p_stat_marshal(&ctx->basectx, out_len, &netstat, (uint8_t *)out));
+}
+
+static void handle_read_dir(struct srv_req *ctx, struct srv_fidinfo *fidinfo, uint64_t offset, uint32_t count) {
+ /* Seek. */
+ if (offset == 0) {
+ fidinfo->dir.idx = 0;
+ fidinfo->dir.off = 0;
+ fidinfo->dir.buffered_dirent = (struct lib9p_srv_dirent){};
+ } else if (offset != fidinfo->dir.off) {
+ srv_respond(ctx, read, NULL, error_new(E_POSIX_EINVAL, "invalid offset (must be 0 or ", fidinfo->dir.off, "): ", offset));
+ return;
+ }
+
+ /* Allocate. */
+ [[gnu::cleanup(heap_cleanup)]] char *heap = NULL;
+ struct lib9p_msg_Rread resp = {
+ .tag = ctx->tag,
+ .data = heap = heap_alloc(count, char),
+ .count = 0,
+ };
+
+ /* Read. */
+ struct srv_pathinfo *dir_pathinfo = NULL;
+ for (;;) {
+ /* 1. Call ->dread() to get `member_dirent`. */
+ struct lib9p_srv_dirent member_dirent;
+ if (fidinfo->dir.buffered_dirent.name.len) {
+ member_dirent = fidinfo->dir.buffered_dirent;
+ } else {
+ lib9p_srv_dirent_or_error r = LO_CALL(fidinfo->dir.io, dread, ctx, fidinfo->dir.idx);
+ if (r.is_err) {
+ if (resp.count) {
+ /* Just do a short-read; discard the error. */
+ error_cleanup(&r.err);
+ break;
+ }
+ srv_respond(ctx, read, NULL, r.err);
+ return;
+ }
+ member_dirent = r.lib9p_srv_dirent;
+ }
+ if (!member_dirent.name.len)
+ /* end-of-directory */
+ break;
+
+ /* 2. Call ->dwalk() to get the `member_file` object to call ->stat() on. */
+ lo_interface lib9p_srv_file member_file;
+ bool free_member_file;
+ {
+ struct srv_pathinfo *member_pathinfo = map_load(&ctx->parent_sess->paths, member_dirent.qid.path);
+ if (member_pathinfo) {
+ member_file = member_pathinfo->file;
+ free_member_file = false;
+ } else {
+ if (!dir_pathinfo)
+ dir_pathinfo = map_load(&ctx->parent_sess->paths, fidinfo->path);
+ assert(dir_pathinfo);
+ lib9p_srv_file_or_error r = LO_CALL(dir_pathinfo->file, dwalk, ctx, member_dirent.name);
+ if (r.is_err) {
+ if (resp.count) {
+ /* Just do a short-read; discard the error. */
+ error_cleanup(&r.err);
+ break;
+ }
+ srv_respond(ctx, read, NULL, r.err);
+ return;
+ }
+ member_file = r.lib9p_srv_file;
+ free_member_file = true;
+ }
+ }
+
+ /* 3. Call ->stat() to get `member_stat``. */
+ /* 4. Encode `member_stat` into `resp.data`/`heap` and increment `resp.count`. */
+ uint32_t_or_error r = stat_and_encode(ctx, member_file, &heap[resp.count], count - resp.count);
+ if (free_member_file)
+ LO_CALL(member_file, free);
+ if (r.is_err) {
+ if (resp.count) {
+ /* Just do a short-read; discard the error. */
+ error_cleanup(&r.err);
+ break;
+ }
+ srv_respond(ctx, read, NULL, r.err);
return;
}
- /* Do it. */
- size_t num = LO_CALL(pathinfo->file, 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;
+ uint32_t nbytes = r.uint32_t;
+ if (!nbytes) {
+ if (resp.count) {
+ /* Just do a short-read; discard the error.
+ * But save the member_dirent for next time. */
+ fidinfo->dir.buffered_dirent = member_dirent;
+ break;
+ }
+ srv_respond(ctx, read, NULL,
+ error_new(E_POSIX_ERANGE, "stat object does not fit into negotiated max message size"));
+ return;
}
- resp->count = len;
- /* Remember. */
- fidinfo->dir_idx = idx+num;
- fidinfo->dir_off = req->offset + len;
- } else
- resp->count = LO_CALL(pathinfo->file, pread, &ctx->ctx, resp->data, req->count, req->offset);
+ resp.count += nbytes;
+ fidinfo->dir.idx++;
+ fidinfo->dir.off += nbytes;
+ fidinfo->dir.buffered_dirent = (struct lib9p_srv_dirent){};
+ }
+ srv_respond(ctx, read, &resp, ERROR_NULL);
}
+#endif
+
+static void handle_Twrite(struct srv_req *ctx,
+ struct lib9p_msg_Twrite *req) {
+ srv_handler_common(ctx, write, req);
-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);
+ /* 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;
+ err = error_new(E_POSIX_EBADF, "bad file number ", req->fid);
+ goto twrite_return;
}
if (!(fidinfo->flags & FIDFLAG_OPEN_W)) {
- lib9p_error(&ctx->ctx.basectx,
- LINUX_EINVAL, "FID not open for writing");
- return;
+ err = error_new(E_POSIX_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(pathinfo->file, pwrite, &ctx->ctx, req->data, req->count, req->offset);
+ ctx->user = srv_userid_incref(fidinfo->user);
+ uint32_t_or_error count = LO_CALL(fidinfo->file.io, pwrite, ctx, req->data, req->count, req->offset);
+ if (count.is_err)
+ err = count.err;
+ else
+ resp.count = count.uint32_t;
+ twrite_return:
+ if (ctx->user)
+ ctx->user = srv_userid_decref(ctx->user);
+ srv_respond(ctx, write, &resp, err);
}
-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_no_err(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);
+ srv_respond(ctx, clunk, NULL, error_new(E_POSIX_EBADF, "bad file number ", req->fid));
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);
- }
- clunk:
- srv_util_pathfree(ctx, LO_CALL(pathinfo->file, qid).path);
- fidmap_del(&ctx->parent_sess->fids, fid);
+ srv_respond(ctx, clunk, &resp, ERROR_NULL);
+ /* Yes, don't actually perform the clunk until *after* we send Rclunk. */
+ srv_fid_del(ctx, req->fid, fidinfo, false);
}
-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) {
+ err = error_new(E_POSIX_EBADF, "bad file number ", 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) {
+ err = error_new(E_POSIX_EBUSY, "cannot remove root");
+ remove = false;
+ goto tremove_main;
+ }
+ struct srv_pathinfo *parent = map_load(&ctx->parent_sess->paths, pathinfo->parent_dir);
+ assert(parent);
+ ok_or_error have_perm = srv_file_check_perm(ctx, parent->file, 0b010);
+ if (have_perm.is_err) {
+ err = have_perm.err;
+ remove = false;
+ goto tremove_main;
+ } else if (!have_perm.ok) {
+ err = error_new(E_POSIX_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, err);
}
-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;
+ err = error_new(E_POSIX_EBADF, "bad file number ", 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);
+ err = stat_and_convert(&resp.stat, ctx, pathinfo->file);
+ tstat_return:
+ if (ctx->user)
+ ctx->user = srv_userid_decref(ctx->user);
+ srv_respond(ctx, stat, &resp, err);
+}
+
+static void handle_Twstat(struct srv_req *ctx,
+ struct lib9p_msg_Twstat *req) {
+ srv_handler_common(ctx, wstat, req);
+
+ err = error_new(E_POSIX_EOPNOTSUPP, "wstat not (yet?) implemented");
+
+ srv_respond(ctx, wstat, &resp, err);
}
+#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);
-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);
+ err = error_new(E_POSIX_EOPNOTSUPP, "openfd not (yet?) implemented");
- lib9p_error(&ctx->ctx.basectx,
- LINUX_EOPNOTSUPP, "wstat not (yet?) implemented");
+ srv_respond(ctx, openfd, &resp, err);
}
+#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->ctx.basectx,
- LINUX_EOPNOTSUPP, "session not (yet?) implemented");
+ err = error_new(E_POSIX_EOPNOTSUPP, "session not (yet?) implemented");
+
+ srv_respond(ctx, session, &resp, err);
}
-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);
+
+ err = error_new(E_POSIX_EOPNOTSUPP, "sread not (yet?) implemented");
- lib9p_error(&ctx->ctx.basectx,
- LINUX_EOPNOTSUPP, "sread not (yet?) implemented");
+ srv_respond(ctx, sread, &resp, err);
}
-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);
+
+ err = error_new(E_POSIX_EOPNOTSUPP, "swrite not (yet?) implemented");
- lib9p_error(&ctx->ctx.basectx,
- LINUX_EOPNOTSUPP, "swrite not (yet?) implemented");
+ srv_respond(ctx, swrite, &resp, err);
}
#endif