/* lib9p/srv.c - 9P server * * Copyright (C) 2024-2025 Luke T. Shumaker * SPDX-License-Identifier: AGPL-3.0-or-later */ #include /* for PRI* */ #include /* for SSIZE_MAX, not set by newlib */ #include /* for size_t */ #include /* for malloc() */ #include /* for memcpy() */ #ifndef SSIZE_MAX #define SSIZE_MAX (SIZE_MAX >> 1) #endif #include #include #include #include #include #include #include #include #define LOG_NAME 9P_SRV #include #define IMPLEMENTATION_FOR_LIB9P_SRV_H YES #include /* 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_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 <= SSIZE_MAX); /* context ********************************************************************/ bool lib9p_srv_flush_requested(struct lib9p_srv_ctx *ctx) { assert(ctx); return cr_chan_can_send(&ctx->flush_ch); } void lib9p_srv_acknowledge_flush(struct lib9p_srv_ctx *ctx) { assert(ctx); 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 * from FIDs. */ unsigned int gc_refcount; /* References from fids with FIDFLAG_OPEN_R/FIDFLAG_OPEN_W. */ unsigned int io_refcount; }; /* 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; 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; }; }; /* contexts ************************************** * * The hierarchy of contexts is: * * server -> connection -> session -> request * */ /* struct lib9p_srv {} is defined in */ struct srv_conn { /* immutable */ struct lib9p_srv *parent_srv; lo_interface net_stream_conn fd; cid_t reader; /* the lib9p_srv_read_cr() coroutine */ /* mutable */ cr_mutex_t writelock; }; #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; enum lib9p_version version; uint32_t max_msg_size; /* mutable */ bool initialized; bool closing; 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 *` */ }; #define srv_req lib9p_srv_ctx /* struct lib9p_srv_ctx {} is defined in */ /* 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_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; } 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 *************************************************************/ 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 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); cr_mutex_unlock(&req->parent_sess->parent_conn->writelock); return r; } #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->basectx.err_msg[0]); ssize_t r; struct lib9p_msg_Rerror host = { .tag = req->tag, .errstr = lib9p_strn(req->basectx.err_msg, CONFIG_9P_MAX_ERR_SIZE), #if CONFIG_9P_ENABLE_9P2000_u .errnum = req->basectx.err_num, #endif }; 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 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->basectx, LIB9P_TYP_Rerror, &host, &net); srv_msglog(req, LIB9P_TYP_Rerror, &host); r = srv_write_Rmsg(req, &net); if (r < 0) srv_nonrespond_errorf("write: %s", net_strerror(-r)); } /* read coroutine *************************************************************/ 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) { srv_nonrespond_errorf("read: %s", net_strerror(-r)); return true; } else if (r == 0) { if (*done != 0) srv_nonrespond_errorf("read: unexpected EOF"); return true; } *done += r; } return false; } 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++; for (;;) { 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) cr_rpc_send_req(&srv->_reqch, NULL); return; } lib9p_srv_read(srv, conn); } } 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_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; } 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 ************************************************************/ void lib9p_srv_worker_loop(struct lib9p_srv *srv) { struct srv_req req; _lib9p_srv_reqch_req_t rpc_handle; assert(srv); assert(srv->rootdir); srv->writers++; for (;;) { /* Receive the request from the reader coroutine. ************/ rpc_handle = cr_rpc_recv_req(&srv->_reqch); if (!rpc_handle.req) { srv->writers--; 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. */ 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. */ cr_rpc_send_resp(rpc_handle, 0); /* Process the request. **************************************/ lib9p_srv_worker(&req); } } #define _HANDLER_PROTO(typ) \ static void handle_T##typ(struct srv_req *, \ struct lib9p_msg_T##typ *) _HANDLER_PROTO(version); _HANDLER_PROTO(auth); _HANDLER_PROTO(attach); _HANDLER_PROTO(flush); _HANDLER_PROTO(walk); _HANDLER_PROTO(open); _HANDLER_PROTO(create); _HANDLER_PROTO(read); _HANDLER_PROTO(write); _HANDLER_PROTO(clunk); _HANDLER_PROTO(remove); _HANDLER_PROTO(stat); _HANDLER_PROTO(wstat); #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 srv_req *, void *); void lib9p_srv_worker(struct srv_req *ctx) { uint8_t *host_req = NULL; /* 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->basectx, ctx->net_bytes, &typ, host_req); srv_msglog(ctx, typ, host_req); /* Handle it. ********************************************************/ tmessage_handler handler; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch-enum" switch (typ) { case LIB9P_TYP_Tversion: handler = (tmessage_handler)handle_Tversion; break; 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; #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 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); } 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 { 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); } 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 srv_req *ctx, struct lib9p_msg_Tversion *req) { srv_handler_common(ctx, version, req); enum lib9p_version version = LIB9P_VER_unknown; if (req->version.len >= 6 && req->version.utf8[0] == '9' && req->version.utf8[1] == 'P' && '0' <= req->version.utf8[2] && req->version.utf8[2] <= '9' && '0' <= req->version.utf8[3] && req->version.utf8[3] <= '9' && '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] == '.')) { version = LIB9P_VER_9P2000; #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; #endif #if CONFIG_9P_ENABLE_9P2000_e if (lib9p_str_eq(lib9p_str_sliceleft(req->version, 6), lib9p_str(".e"))) version = LIB9P_VER_9P2000_e; #endif } uint32_t min_msg_size = lib9p_version_min_msg_size(version); if (req->max_msg_size < min_msg_size) { 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); goto tversion_return; } 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 (map_len(&ctx->parent_sess->reqs)) { /* Flush all in-progress requests, and wait for them * to finish. */ 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; MAP_FOREACH(&ctx->parent_sess->reqs, tag, reqpp) { enum _lib9p_srv_flush_result flushed; args[i++] = CR_SELECT_RECV(&((*reqpp)->flush_ch), &flushed); } assert(i == map_len(&ctx->parent_sess->reqs)); cr_select_v(i, args); } } /* 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; tversion_return: srv_respond(ctx, version, &resp); } static void handle_Tauth(struct srv_req *ctx, struct lib9p_msg_Tauth *req) { srv_handler_common(ctx, auth, req); struct lib9p_srv *srv = ctx->parent_sess->parent_conn->parent_srv; if (!srv->auth) { lib9p_error(&ctx->basectx, LIB9P_ERRNO_L_EOPNOTSUPP, "authentication not required"); goto tauth_return; } 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 srv_req *ctx, struct lib9p_msg_Tattach *req) { srv_handler_common(ctx, attach, req); 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 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); #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 (!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->basectx, LIB9P_ERRNO_L_EACCES, "FID provided as auth-file, but no auth-file is required"); goto tattach_return; } ctx->user = srv_userid_new(req->uname, req->unum); } /* 1. File object */ 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(srv_qid_filetype(root_qid) == SRV_FILETYPE_DIR); /* 2. pathinfo */ struct srv_pathinfo *root_pathinfo = srv_path_save(ctx, root_file, root_qid.path); /* 3. fidinfo */ if (!srv_fid_store(ctx, req->fid, root_pathinfo, false)) { srv_path_decref(ctx, root_qid.path); goto tattach_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 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 srv_req *ctx, struct lib9p_msg_Twalk *req) { srv_handler_common(ctx, walk, req); if (req->newfid == LIB9P_FID_NOFID) { lib9p_error(&ctx->basectx, LIB9P_ERRNO_L_EBADF, "cannot assign to NOFID"); goto twalk_return; } 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 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 = map_load(&ctx->parent_sess->paths, fidinfo->path); assert(pathinfo); pathinfo->gc_refcount++; 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 = map_load(&ctx->parent_sess->paths, pathinfo->parent_dir); assert(new_pathinfo); new_pathinfo->gc_refcount++; } else { 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_path_save(ctx, member_file, LO_CALL(pathinfo->file, qid).path); assert(new_pathinfo); } if (new_pathinfo->type == SRV_FILETYPE_DIR) { struct lib9p_stat stat = LO_CALL(new_pathinfo->file, stat, ctx); if (lib9p_ctx_has_error(&ctx->basectx)) break; lib9p_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); srv_path_decref(ctx, LO_CALL(pathinfo->file, qid).path); pathinfo = new_pathinfo; } 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->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 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 = 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 topen_return; } if (fidinfo->flags & FIDFLAG_OPEN) { lib9p_error(&ctx->basectx, LIB9P_ERRNO_L_EALREADY, "FID is already open"); goto topen_return; } 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->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; 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 = map_load(&ctx->parent_sess->paths, pathinfo->parent_dir); assert(parent); struct lib9p_stat parent_stat = LO_CALL(parent->file, stat, ctx); if (lib9p_ctx_has_error(&ctx->basectx)) goto topen_return; lib9p_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); if (lib9p_ctx_has_error(&ctx->basectx)) goto topen_return; lib9p_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.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_MODE_READ: perm_bits = 0b100; rd = true; break; case LIB9P_O_MODE_WRITE: perm_bits = 0b010; wr = true; break; case LIB9P_O_MODE_RDWR: perm_bits = 0b110; rd = wr = true; break; case LIB9P_O_MODE_EXEC: perm_bits = 0b001; rd = true; break; } 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; 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; 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->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. */ if (rd) fidflags |= FIDFLAG_OPEN_R; if (wr) fidflags |= FIDFLAG_OPEN_W; pathinfo->io_refcount++; fidinfo->flags = fidflags; 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 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"); srv_respond(ctx, create, &resp); } 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 = 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 tread_return; } if (!(fidinfo->flags & FIDFLAG_OPEN_R)) { lib9p_error(&ctx->basectx, LIB9P_ERRNO_L_EINVAL, "FID not open for reading"); goto tread_return; } /* Do it. */ ctx->user = srv_userid_incref(fidinfo->user); switch (fidinfo->type) { case SRV_FILETYPE_DIR: /* 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); goto tread_return; } /* 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_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_stat_assert(member_stat); uint32_t nbytes = lib9p_stat_marshal(&ctx->basectx, req->count-resp.count, &member_stat, (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){}; } break; case SRV_FILETYPE_FILE: struct iovec iov; 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 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 = 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 twrite_return; } if (!(fidinfo->flags & FIDFLAG_OPEN_W)) { lib9p_error(&ctx->basectx, LIB9P_ERRNO_L_EINVAL, "FID not open for writing"); goto twrite_return; } if (fidinfo->flags & FIDFLAG_APPEND) req->offset = 0; /* Do it. */ 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 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->basectx, LIB9P_ERRNO_L_EBADF, "bad file number %"PRIu32, req->fid); goto tclunk_return; } srv_fid_del(ctx, req->fid, fidinfo, false); tclunk_return: srv_respond(ctx, clunk, &resp); } 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; } 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_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; } tremove_main: srv_fid_del(ctx, req->fid, fidinfo, remove); tremove_return: srv_respond(ctx, remove, &resp); } static void handle_Tstat(struct srv_req *ctx, struct lib9p_msg_Tstat *req) { srv_handler_common(ctx, stat, 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 tstat_return; } struct srv_pathinfo *pathinfo = map_load(&ctx->parent_sess->paths, fidinfo->path); assert(pathinfo); ctx->user = srv_userid_incref(fidinfo->user); resp.stat = LO_CALL(pathinfo->file, stat, ctx); if (!lib9p_ctx_has_error(&ctx->basectx)) lib9p_stat_assert(resp.stat); tstat_return: if (ctx->user) ctx->user = srv_userid_decref(ctx->user); srv_respond(ctx, stat, &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"); srv_respond(ctx, wstat, &resp); } #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 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"); srv_respond(ctx, session, &resp); } static void handle_Tsread(struct srv_req *ctx, struct lib9p_msg_Tsread *req) { srv_handler_common(ctx, sread, req); lib9p_error(&ctx->basectx, LIB9P_ERRNO_L_EOPNOTSUPP, "sread not (yet?) implemented"); srv_respond(ctx, sread, &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"); srv_respond(ctx, swrite, &resp); } #endif