#include #include /* for PRI* */ #include /* for fprintf(), stderr */ #include /* for strerror() */ #include #include #include #include #include #include "internal.h" /* structs ********************************************************************/ #define NAME fidmap #define KEY_T uint32_t #define VAL_T struct lib9p_srv_file #define CAP CONFIG_9P_MAX_FIDS #include "map.h" #define NAME reqmap #define KEY_T uint32_t #define VAL_T struct lib9p_srv_reqctx #define CAP CONFIG_9P_MAX_REQS #include "map.h" /* The hierarchy of concepts is: * * server -> connection -> session -> request * */ /* struct lib9p_srv {} is defined in */ struct lib9p_conn { /* immutable */ struct lib9p_srv *parent_srv; int fd; cid_t reader; /* the lib9p_srv_read_cr() coroutine */ /* mutable */ cr_mutex_t writelock; }; struct lib9p_sess { /* immutable */ struct lib9p_conn *parent_conn; enum lib9p_version version; uint32_t max_msg_size; uint32_t rerror_overhead; /* mutable */ bool initialized; bool closing; struct reqmap reqs; struct fidmap fids; }; struct lib9p_req { /* immutable */ struct lib9p_sess *parent_sess; uint16_t tag; /* mutable */ uint8_t *net_bytes; /* CONFIG_9P_MAX_MSG_SIZE-sized */ struct lib9p_ctx ctx; }; /* base utilities *************************************************************/ #define nonrespond_errorf(fmt, ...) fprintf(stderr, "error: " fmt "\n" __VA_OPT__(,) __VA_ARGS__) static uint32_t rerror_overhead_for_version(enum lib9p_version version, uint8_t *scratch) { struct lib9p_ctx empty_ctx = { .version = version, .max_msg_size = CONFIG_9P_MAX_MSG_SIZE, }; struct lib9p_msg_Rerror empty_error = { 0 }; assert(!lib9p_marshal(&empty_ctx, LIB9P_TYP_Rerror, 0, &empty_error, scratch)); uint32_t min_msg_size = decode_u32le(scratch); assert(min_msg_size < (UINT32_MAX - UINT16_MAX)); assert(CONFIG_9P_MAX_MSG_SIZE >= min_msg_size); return min_msg_size; } static void respond_error(struct lib9p_req *req) { assert(req->ctx.err_num); assert(req->ctx.err_msg[0]); ssize_t r; struct lib9p_msg_Rerror host = { .ename = { .len = strnlen(req->ctx.err_msg, CONFIG_9P_MAX_ERR_SIZE), .utf8 = req->ctx.err_msg, }, .errno = req->ctx.err_num, }; /* Truncate the error-string if necessary to avoid needing to return ERANGE. */ if (((uint32_t)host.ename.len) + req->parent_sess->rerror_overhead > req->parent_sess->max_msg_size) host.ename.len = req->parent_sess->max_msg_size - req->parent_sess->rerror_overhead; lib9p_marshal(&req->ctx, LIB9P_TYP_Rerror, req->tag, &host, req->net_bytes); cr_mutex_lock(&req->parent_sess->parent_conn->writelock); r = netio_write(req->parent_sess->parent_conn->fd, req->net_bytes, decode_u32le(req->net_bytes)); cr_mutex_unlock(&req->parent_sess->parent_conn->writelock); if (r < 0) nonrespond_errorf("write: %s", strerror(-r)); } /* read coroutine *************************************************************/ static bool read_at_least(int fd, uint8_t *buf, size_t goal, size_t *done) { assert(buf); assert(goal); assert(done); while (*done < goal) { ssize_t r = netio_read(fd, &buf[*done], CONFIG_9P_MAX_MSG_SIZE - *done); if (r < 0) { nonrespond_errorf("read: %s", strerror(-r)); return true; } else if (r == 0) { if (*done != 0) nonrespond_errorf("read: unexpected EOF"); return true; } *done += r; } return false; } static bool handle_Tmessage(struct lib9p_req *); COROUTINE lib9p_srv_read_cr(void *_srv) { uint8_t buf[CONFIG_9P_MAX_MSG_SIZE]; struct lib9p_srv *srv = _srv; assert(srv); cr_begin(); uint32_t initial_rerror_overhead = rerror_overhead_for_version(0, buf); for (;;) { struct lib9p_conn conn = { .parent_srv = srv, .fd = netio_accept(srv->sockfd), .reader = cr_getcid(), }; if (conn.fd < 0) { nonrespond_errorf("accept: %s", strerror(-conn.fd)); continue; } struct lib9p_sess sess = { .parent_conn = &conn, .version = 0, .max_msg_size = CONFIG_9P_MAX_MSG_SIZE, .rerror_overhead = initial_rerror_overhead, .initialized = false, }; for (;;) { /* Read the message. */ size_t done = 0; if (read_at_least(conn.fd, buf, 4, &done)) goto close; size_t goal = decode_u32le(buf); if (goal < 7) { nonrespond_errorf("T-message is impossibly small"); goto close; } if (read_at_least(conn.fd, buf, 7, &done)) goto close; struct lib9p_req req = { .parent_sess = &sess, .tag = decode_u16le(&buf[5]), .net_bytes = buf, .ctx = { .version = sess.version, .max_msg_size = sess.max_msg_size, }, }; if (goal > sess.max_msg_size) { lib9p_errorf(&req.ctx, LINUX_EMSGSIZE, "T-message larger than %s limit (%zu > %"PRIu32")", sess.initialized ? "negotiated" : "server", goal, sess.max_msg_size); respond_error(&req); continue; } if (read_at_least(conn.fd, buf, goal, &done)) goto close; /* Handle the message... in another coroutine. */ cr_chan_send(&srv->reqch, &req); cr_pause_and_yield(); /* wait for it to have copied req */ } close: netio_close(conn.fd, true, sess.reqs.len == 0); if (sess.reqs.len) { sess.closing = true; cr_pause_and_yield(); assert(sess.reqs.len == 0); netio_close(conn.fd, false, true); } } cr_end(); } /* write coroutine ************************************************************/ static void handle_Tversion(struct lib9p_req *ctx, struct lib9p_msg_Tversion *req, struct lib9p_msg_Rversion *resp); static void handle_Tauth(struct lib9p_req *ctx, struct lib9p_msg_Tauth *req, struct lib9p_msg_Rauth *resp); static void handle_Tattach(struct lib9p_req *ctx, struct lib9p_msg_Tattach *req, struct lib9p_msg_Rattach *resp); static void handle_Tflush(struct lib9p_req *ctx, struct lib9p_msg_Tflush *req, struct lib9p_msg_Rflush *resp); static void handle_Twalk(struct lib9p_req *ctx, struct lib9p_msg_Twalk *req, struct lib9p_msg_Rwalk *resp); static void handle_Topen(struct lib9p_req *ctx, struct lib9p_msg_Topen *req, struct lib9p_msg_Ropen *resp); static void handle_Tcreate(struct lib9p_req *ctx, struct lib9p_msg_Tcreate *req, struct lib9p_msg_Rcreate *resp); static void handle_Tread(struct lib9p_req *ctx, struct lib9p_msg_Tread *req, struct lib9p_msg_Rread *resp); static void handle_Twrite(struct lib9p_req *ctx, struct lib9p_msg_Twrite *req, struct lib9p_msg_Rwrite *resp); static void handle_Tclunk(struct lib9p_req *ctx, struct lib9p_msg_Tclunk *req, struct lib9p_msg_Rclunk *resp); static void handle_Tremove(struct lib9p_req *ctx, struct lib9p_msg_Tremove *req, struct lib9p_msg_Rremove *resp); static void handle_Tstat(struct lib9p_req *ctx, struct lib9p_msg_Tstat *req, struct lib9p_msg_Rstat *resp); static void handle_Twstat(struct lib9p_req *ctx, struct lib9p_msg_Twstat *req, struct lib9p_msg_Rwstat *resp); static void handle_Tsession(struct lib9p_req *ctx, struct lib9p_msg_Tsession *req, struct lib9p_msg_Rsession *resp); static void handle_Tsread(struct lib9p_req *ctx, struct lib9p_msg_Tsread *req, struct lib9p_msg_Rsread *resp); static void handle_Tswrite(struct lib9p_req *ctx, struct lib9p_msg_Tswrite *req, struct lib9p_msg_Rswrite *resp); COROUTINE lib9p_srv_write_cr(void *_srv) { uint8_t net[CONFIG_9P_MAX_MSG_SIZE]; struct lib9p_srv *srv = _srv; assert(srv); cr_begin(); for (;;) { /* Receive the request from the reader coroutine. */ struct lib9p_req req; struct lib9p_req *_req_p; cr_chan_recv(&srv->reqch, &_req_p); req = *_req_p; memcpy(net, req.net_bytes, decode_u32le(req.net_bytes)); req.net_bytes = net; cr_unpause(req.parent_sess->parent_conn->reader); /* notify that we've copied req */ handle_Tmessage(&req); 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); } cr_end(); } static bool handle_Tmessage(struct lib9p_req *req) { uint8_t host_req[CONFIG_9P_MAX_HOSTMSG_SIZE]; uint8_t host_resp[CONFIG_9P_MAX_HOSTMSG_SIZE]; /* Unmarshal it. */ enum lib9p_msg_type typ = req->net_bytes[4]; if (typ % 2 != 0) { lib9p_errorf(&req->ctx, LINUX_EOPNOTSUPP, "expected a T-message but got an R-message: message_type=%s", lib9p_msg_type_str(typ)); goto write; } ssize_t host_size = lib9p_validate(&req->ctx, req->net_bytes); if (host_size < 0) goto write; if ((size_t)host_size > sizeof(host_req)) { lib9p_errorf(&req->ctx, LINUX_EMSGSIZE, "unmarshalled payload larger than server limit (%zu > %zu)", host_size, sizeof(host_req)); goto write; } lib9p_unmarshal(&req->ctx, req->net_bytes, &typ, &req->tag, host_req); /* Handle it. */ switch (typ) { case LIB9P_TYP_Tversion: handle_Tversion(req, (struct lib9p_msg_Tversion *)host_req, (struct lib9p_msg_Rversion *)host_resp); break; case LIB9P_TYP_Tauth: handle_Tauth(req, (struct lib9p_msg_Tauth *)host_req, (struct lib9p_msg_Rauth *)host_resp); break; case LIB9P_TYP_Tattach: handle_Tattach(req, (struct lib9p_msg_Tattach *)host_req, (struct lib9p_msg_Rattach *)host_resp); break; case LIB9P_TYP_Tflush: handle_Tflush(req, (struct lib9p_msg_Tflush *)host_req, (struct lib9p_msg_Rflush *)host_resp); break; case LIB9P_TYP_Twalk: handle_Twalk(req, (struct lib9p_msg_Twalk *)host_req, (struct lib9p_msg_Rwalk *)host_resp); break; case LIB9P_TYP_Topen: handle_Topen(req, (struct lib9p_msg_Topen *)host_req, (struct lib9p_msg_Ropen *)host_resp); break; case LIB9P_TYP_Tcreate: handle_Tcreate(req, (struct lib9p_msg_Tcreate *)host_req, (struct lib9p_msg_Rcreate *)host_resp); break; case LIB9P_TYP_Tread: handle_Tread(req, (struct lib9p_msg_Tread *)host_req, (struct lib9p_msg_Rread *)host_resp); break; case LIB9P_TYP_Twrite: handle_Twrite(req, (struct lib9p_msg_Twrite *)host_req, (struct lib9p_msg_Rwrite *)host_resp); break; case LIB9P_TYP_Tclunk: handle_Tclunk(req, (struct lib9p_msg_Tclunk *)host_req, (struct lib9p_msg_Rclunk *)host_resp); break; case LIB9P_TYP_Tremove: handle_Tremove(req, (struct lib9p_msg_Tremove *)host_req, (struct lib9p_msg_Rremove *)host_resp); break; case LIB9P_TYP_Tstat: handle_Tstat(req, (struct lib9p_msg_Tstat *)host_req, (struct lib9p_msg_Rstat *)host_resp); break; case LIB9P_TYP_Twstat: handle_Twstat(req, (struct lib9p_msg_Twstat *)host_req, (struct lib9p_msg_Rwstat *)host_resp); break; case LIB9P_TYP_Tsession: /* 9P2000.e */ handle_Tsession(req, (struct lib9p_msg_Tsession *)host_req, (struct lib9p_msg_Rsession *)host_resp); break; case LIB9P_TYP_Tsread: /* 9P2000.e */ handle_Tsread(req, (struct lib9p_msg_Tsread *)host_req, (struct lib9p_msg_Rsread *)host_resp); break; case LIB9P_TYP_Tswrite: /* 9P2000.e */ handle_Tswrite(req, (struct lib9p_msg_Tswrite *)host_req, (struct lib9p_msg_Rswrite *)host_resp); break; default: assert(false); } write: if (lib9p_ctx_has_error(&req->ctx)) { respond_error(req); return true; } else { if (lib9p_marshal(&req->ctx, typ+1, req->tag, host_resp, req->net_bytes)) goto write; cr_mutex_lock(&req->parent_sess->parent_conn->writelock); netio_write(req->parent_sess->parent_conn->fd, req->net_bytes, decode_u32le(req->net_bytes)); cr_mutex_unlock(&req->parent_sess->parent_conn->writelock); return false; } } static void handle_Tversion(struct lib9p_req *ctx, struct lib9p_msg_Tversion *req, struct lib9p_msg_Rversion *resp) { 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.utf8[6] == '\0' || req->version.utf8[6] == '.')) { if (strcmp((char *)&req->version.utf8[6], ".u") == 0) version = LIB9P_VER_9P2000_u; else if (strcmp((char *)&req->version.utf8[6], ".e") == 0) version = LIB9P_VER_9P2000_e; else version = LIB9P_VER_9P2000; } uint32_t min_msg_size = rerror_overhead_for_version(version, ctx->net_bytes); if (req->max_msg_size < min_msg_size) { lib9p_errorf(&ctx->ctx, LINUX_EDOM, "requested max_msg_size is less than minimum for %s (%"PRIu32" < %"PRIu32")", version, req->max_msg_size, min_msg_size); return; } struct lib9p_srv_reqctx subctx = { .base = &ctx->ctx, }; if (ctx->parent_sess->reqs.len) { ctx->parent_sess->closing = true; // TODO: send flush events cr_pause_and_yield(); assert(ctx->parent_sess->reqs.len == 0); } if (ctx->parent_sess->fids.len) { uint32_t fid; struct lib9p_srv_file *fptr; MAP_FOREACH(&ctx->parent_sess->fids, fid, fptr) { fptr->vtable.free(&subctx, fptr->impldata); fidmap_del(&ctx->parent_sess->fids, fid); } } // TODO: replace session with a new one? #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdiscarded-qualifiers" resp->version.utf8 = lib9p_version_str(version); #pragma GCC diagnostic pop resp->version.len = strlen((char *)resp->version.utf8); resp->max_msg_size = (CONFIG_9P_MAX_MSG_SIZE < req->max_msg_size) ? CONFIG_9P_MAX_MSG_SIZE : req->max_msg_size; } static void handle_Tauth(struct lib9p_req *ctx, struct lib9p_msg_Tauth *req, struct lib9p_msg_Rauth *UNUSED(resp)) { if (!ctx->parent_sess->parent_conn->parent_srv->auth) { lib9p_error(&ctx->ctx, LINUX_EOPNOTSUPP, "authentication not required"); return; } struct lib9p_srv_reqctx subctx = { .base = &ctx->ctx, .uid = req->n_uname, .uname = req->uname.utf8, }; ctx->parent_sess->parent_conn->parent_srv->auth(&subctx, req->aname.utf8); lib9p_error(&ctx->ctx, LINUX_EOPNOTSUPP, "TODO: auth not implemented"); } static void handle_Tattach(struct lib9p_req *ctx, struct lib9p_msg_Tattach *req, struct lib9p_msg_Rattach *resp) { struct lib9p_srv_reqctx subctx = { .base = &ctx->ctx, .uid = req->n_uname, .uname = req->uname.utf8, }; if (ctx->parent_sess->parent_conn->parent_srv->auth) { /* struct lib9p_srv_filehandle *fh = fidmap_get(req->afid); if (!fh) lib9p_error(&ctx->ctx, LINUX_EACCES, "FID provided as auth-file is not a valid FID"); else if (fh->type != FH_AUTH) lib9p_error(&ctx->ctx, LINUX_EACCES, "FID provided as auth-file is not an auth-file"); else if (strcmp(fh->data.auth.uname, req->uname.utf8) != 0) lib9p_errorf(&ctx->ctx, LINUX_EACCES, "FID provided as auth-file is for user=\"%s\" and cannot be used for user=\"%s\"", fh->data.auth.uname, req->uname.utf8); else if (strcmp(fh->data.auth.aname, req->aname.utf8) != 0) lib9p_errorf(&ctx->ctx, LINUX_EACCES, "FID provided as auth-file is for tree=\"%s\" and cannot be used for tree=\"%s\"", fh->data.auth.aname, req->aname.utf8); else if (!fh->data.auth.authenticated) lib9p_error(&ctx->ctx, 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, LINUX_EOPNOTSUPP, "TODO: auth not implemented"); return; } else { if (req->afid != LIB9P_NOFID) { lib9p_error(&ctx->ctx, LINUX_EACCES, "FID provided as auth-file, but no auth-file is required"); return; } } if (fidmap_load(&ctx->parent_sess->fids, req->fid)) { lib9p_error(&ctx->ctx, LINUX_EBADF, "FID already in use"); return; } struct lib9p_srv_file rootdir = ctx->parent_sess->parent_conn->parent_srv->rootdir(&subctx, req->aname.utf8); if (lib9p_ctx_has_error(&ctx->ctx)) return; struct lib9p_stat stat = rootdir.vtable.stat(&subctx, rootdir.impldata); if (lib9p_ctx_has_error(&ctx->ctx)) { if (rootdir.vtable.free) rootdir.vtable.free(&subctx, rootdir.impldata); return; } struct lib9p_srv_file *rootdir_ptr = fidmap_store(&ctx->parent_sess->fids, req->fid, rootdir); if (!rootdir_ptr) { lib9p_error(&ctx->ctx, LINUX_EMFILE, "too many open files"); if (rootdir.vtable.free) rootdir.vtable.free(&subctx, rootdir.impldata); return; } resp->qid = stat.file_qid; return; } static void handle_Tflush(struct lib9p_req *ctx, struct lib9p_msg_Tflush *UNUSED(req), struct lib9p_msg_Rflush *UNUSED(resp)) { lib9p_error(&ctx->ctx, LINUX_EOPNOTSUPP, "flush not yet implemented"); } static void handle_Twalk(struct lib9p_req *ctx, struct lib9p_msg_Twalk *UNUSED(req), struct lib9p_msg_Rwalk *UNUSED(resp)) { lib9p_error(&ctx->ctx, LINUX_EOPNOTSUPP, "walk not yet implemented"); } static void handle_Topen(struct lib9p_req *ctx, struct lib9p_msg_Topen *UNUSED(req), struct lib9p_msg_Ropen *UNUSED(resp)) { lib9p_error(&ctx->ctx, LINUX_EOPNOTSUPP, "open not yet implemented"); } static void handle_Tcreate(struct lib9p_req *ctx, struct lib9p_msg_Tcreate *UNUSED(req), struct lib9p_msg_Rcreate *UNUSED(resp)) { lib9p_error(&ctx->ctx, LINUX_EOPNOTSUPP, "create not yet implemented"); } static void handle_Tread(struct lib9p_req *ctx, struct lib9p_msg_Tread *UNUSED(req), struct lib9p_msg_Rread *UNUSED(resp)) { lib9p_error(&ctx->ctx, LINUX_EOPNOTSUPP, "read not yet implemented"); } static void handle_Twrite(struct lib9p_req *ctx, struct lib9p_msg_Twrite *UNUSED(req), struct lib9p_msg_Rwrite *UNUSED(resp)) { lib9p_error(&ctx->ctx, LINUX_EOPNOTSUPP, "write not yet implemented"); } static void handle_Tclunk(struct lib9p_req *ctx, struct lib9p_msg_Tclunk *UNUSED(req), struct lib9p_msg_Rclunk *UNUSED(resp)) { lib9p_error(&ctx->ctx, LINUX_EOPNOTSUPP, "clunk not yet implemented"); } static void handle_Tremove(struct lib9p_req *ctx, struct lib9p_msg_Tremove *UNUSED(req), struct lib9p_msg_Rremove *UNUSED(resp)) { lib9p_error(&ctx->ctx, LINUX_EOPNOTSUPP, "remove not yet implemented"); } static void handle_Tstat(struct lib9p_req *ctx, struct lib9p_msg_Tstat *UNUSED(req), struct lib9p_msg_Rstat *UNUSED(resp)) { lib9p_error(&ctx->ctx, LINUX_EOPNOTSUPP, "stat not yet implemented"); } static void handle_Twstat(struct lib9p_req *ctx, struct lib9p_msg_Twstat *UNUSED(req), struct lib9p_msg_Rwstat *UNUSED(resp)) { lib9p_error(&ctx->ctx, LINUX_EOPNOTSUPP, "wstat not yet implemented"); } static void handle_Tsession(struct lib9p_req *ctx, struct lib9p_msg_Tsession *UNUSED(req), struct lib9p_msg_Rsession *UNUSED(resp)) { lib9p_error(&ctx->ctx, LINUX_EOPNOTSUPP, "session not yet implemented"); } static void handle_Tsread(struct lib9p_req *ctx, struct lib9p_msg_Tsread *UNUSED(req), struct lib9p_msg_Rsread *UNUSED(resp)) { lib9p_error(&ctx->ctx, LINUX_EOPNOTSUPP, "sread not yet implemented"); } static void handle_Tswrite(struct lib9p_req *ctx, struct lib9p_msg_Tswrite *UNUSED(req), struct lib9p_msg_Rswrite *UNUSED(resp)) { lib9p_error(&ctx->ctx, LINUX_EOPNOTSUPP, "swrite not yet implemented"); }