/* lib9p/srv.c - 9P server * * Copyright (C) 2024 Luke T. Shumaker * SPDX-Licence-Identifier: AGPL-3.0-or-later */ #include #include #include /* for PRI* */ #include /* for fprintf(), stderr */ #include /* for strerror() */ #include #include #include #include #include #include #include "internal.h" /* structs ********************************************************************/ #define FIDFLAG_OPEN_R (1<<0) #define FIDFLAG_OPEN_W (1<<1) #define FIDFLAG_RCLOSE (1<<2) #define FIDFLAG_ISDIR (1<<3) #define FIDFLAG_OPEN (FIDFLAG_OPEN_R|FIDFLAG_OPEN_W) struct _srv_fidinfo { struct lib9p_srv_file *file; uint8_t flags; size_t dir_idx; uint32_t dir_off; }; #define NAME fidmap #define KEY_T uint32_t #define VAL_T struct _srv_fidinfo #define CAP CONFIG_9P_MAX_FIDS #include "map.h" #define NAME reqmap #define KEY_T uint32_t #define VAL_T struct _lib9p_srv_req * #define CAP CONFIG_9P_MAX_REQS #include "map.h" /* The hierarchy of concepts is: * * server -> connection -> session -> request * */ /* struct _srv_srv {} is defined in */ struct _srv_conn { /* immutable */ struct lib9p_srv *parent_srv; implements_net_conn *fd; cid_t reader; /* the lib9p_srv_read_cr() coroutine */ /* mutable */ cr_mutex_t writelock; }; struct _srv_sess { /* immutable */ struct _srv_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_srv_req { /* immutable */ struct _srv_sess *parent_sess; uint16_t tag; /* mutable */ uint8_t *net_bytes; /* CONFIG_9P_MAX_MSG_SIZE-sized */ struct lib9p_srv_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 }; bool e; e = lib9p_marshal(&empty_ctx, LIB9P_TYP_Rerror, &empty_error, /* host_body */ scratch); /* net_bytes */ assert(!e); uint32_t min_msg_size = decode_u32le(scratch); /* Assert that min_msg_size + biggest_possible_MAX_ERR_SIZE * won't overflow uint32... because using * __builtin_add_overflow in respond_error() would be a bit * much. */ assert(min_msg_size < (UINT32_MAX - UINT16_MAX)); /* Assert that min_msg_size doesn't overflow MAX_MSG_SIZE. */ assert(CONFIG_9P_MAX_MSG_SIZE >= min_msg_size); return min_msg_size; } static void respond_error(struct _lib9p_srv_req *req) { #ifdef CONFIG_9P_ENABLE_9P2000_u assert(req->ctx.basectx.err_num); #endif assert(req->ctx.basectx.err_msg[0]); ssize_t r; struct lib9p_msg_Rerror host = { .tag = req->tag, .ename = { .len = strnlen(req->ctx.basectx.err_msg, CONFIG_9P_MAX_ERR_SIZE), .utf8 = req->ctx.basectx.err_msg, }, #ifdef CONFIG_9P_ENABLE_9P2000_u .errno = req->ctx.basectx.err_num, #endif }; struct _srv_sess *sess = req->parent_sess; /* Truncate the error-string if necessary to avoid needing to * return ERANGE. The assert() in * rerror_overhead_for_version() has checked that this * addition doesn't overflow. */ if (((uint32_t)host.ename.len) + sess->rerror_overhead > sess->max_msg_size) host.ename.len = sess->max_msg_size - sess->rerror_overhead; lib9p_marshal(&req->ctx.basectx, LIB9P_TYP_Rerror, &host, req->net_bytes); cr_mutex_lock(&sess->parent_conn->writelock); r = VCALL(sess->parent_conn->fd, write, req->net_bytes, decode_u32le(req->net_bytes)); cr_mutex_unlock(&sess->parent_conn->writelock); if (r < 0) nonrespond_errorf("write: %s", strerror(-r)); } /* read coroutine *************************************************************/ static bool read_at_least(implements_net_conn *fd, uint8_t *buf, size_t goal, size_t *done) { assert(buf); assert(goal); assert(done); while (*done < goal) { ssize_t r = VCALL(fd, read, &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 void handle_message(struct _lib9p_srv_req *ctx); __attribute__ ((noreturn)) void lib9p_srv_read_cr(struct lib9p_srv *srv, implements_net_listener *listener) { uint8_t buf[CONFIG_9P_MAX_MSG_SIZE]; assert(srv); assert(srv->rootdir); assert(listener); uint32_t initial_rerror_overhead = rerror_overhead_for_version(0, buf); for (;;) { struct _srv_conn conn = { .parent_srv = srv, .fd = VCALL(listener, accept), .reader = cr_getcid(), }; if (!conn.fd) { nonrespond_errorf("accept: error"); continue; } 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; 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_srv_req req = { .parent_sess = &sess, .tag = decode_u16le(&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; } if (read_at_least(conn.fd, buf, goal, &done)) goto close; /* Handle the message... */ if (buf[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); } close: VCALL(conn.fd, close, true, sess.reqs.len == 0); if (sess.reqs.len) { sess.closing = true; cr_pause_and_yield(); assert(sess.reqs.len == 0); VCALL(conn.fd, close, true, true); } } } /* write coroutine ************************************************************/ COROUTINE lib9p_srv_write_cr(void *_srv) { uint8_t net[CONFIG_9P_MAX_MSG_SIZE]; struct _lib9p_srv_req req; _lib9p_srv_reqch_req_t rpc_handle; struct lib9p_srv *srv = _srv; assert(srv); assert(srv->rootdir); cr_begin(); for (;;) { /* Receive the request from the reader coroutine. ************/ rpc_handle = _lib9p_srv_reqch_recv_req(&srv->_reqch); /* Deep-copy the request from the reader coroutine's * stack to our stack. */ req = *rpc_handle.req; memcpy(net, req.net_bytes, decode_u32le(req.net_bytes)); req.net_bytes = net; /* Record that we have it. */ reqmap_store(&req.parent_sess->reqs, req.tag, &req); /* Notify the reader coroutine that we're done with * its data. */ _lib9p_srv_reqch_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); } 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 *) _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); #ifdef 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, #ifdef 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[CONFIG_9P_MAX_HOSTMSG_SIZE]; uint8_t host_resp[CONFIG_9P_MAX_HOSTMSG_SIZE]; /* Unmarshal it. */ enum lib9p_msg_type typ = ctx->net_bytes[4]; if (typ % 2 != 0) { lib9p_errorf(&ctx->ctx.basectx, LINUX_EOPNOTSUPP, "expected a T-message but got an R-message: message_type=%s", lib9p_msg_type_str(&ctx->ctx.basectx, typ)); goto write; } ssize_t host_size = lib9p_validate(&ctx->ctx.basectx, ctx->net_bytes); if (host_size < 0) goto write; if ((size_t)host_size > sizeof(host_req)) { lib9p_errorf(&ctx->ctx.basectx, LINUX_EMSGSIZE, "unmarshalled payload larger than server limit (%zu > %zu)", host_size, sizeof(host_req)); goto write; } lib9p_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 { if (lib9p_marshal(&ctx->ctx.basectx, typ+1, host_resp, ctx->net_bytes)) goto write; cr_mutex_lock(&ctx->parent_sess->parent_conn->writelock); VCALL(ctx->parent_sess->parent_conn->fd, write, ctx->net_bytes, decode_u32le(ctx->net_bytes)); cr_mutex_unlock(&ctx->parent_sess->parent_conn->writelock); } } #define util_handler_common(ctx, req, resp) do { \ assert(ctx); \ assert(req); \ assert(resp); \ resp->tag = req->tag; \ } while (0) static inline bool util_check_perm(struct lib9p_srv_ctx *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 inline bool util_release(struct lib9p_srv_ctx *ctx, struct lib9p_srv_file *file) { assert(file); file->_refcount--; if (file->_refcount == 0) { if (file->_parent_dir != file) util_release(ctx, file->_parent_dir); VCALL(file, free, ctx); } return lib9p_ctx_has_error(&ctx->basectx); } 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); 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] == '.')) { version = LIB9P_VER_9P2000; #ifdef CONFIG_9P_ENABLE_9P2000_u if (strcmp(&req->version.utf8[6], ".u") == 0) version = LIB9P_VER_9P2000_u; #endif #ifdef CONFIG_9P_ENABLE_9P2000_e if (strcmp(&req->version.utf8[6], ".e") == 0) version = LIB9P_VER_9P2000_e; #endif } 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.basectx, LINUX_EDOM, "requested max_msg_size is less than minimum for %s (%"PRIu32" < %"PRIu32")", version, req->max_msg_size, min_msg_size); return; } #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(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; /* Close the old session. */ if (ctx->parent_sess->reqs.len) { /* 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 __attribute__((unused)); struct _lib9p_srv_req **reqpp; size_t i = 0; bool flushed; MAP_FOREACH(&ctx->parent_sess->reqs, tag, reqpp) { list[i] = CR_SELECT_RECV(&((*reqpp)->ctx._flushch), &flushed); } cr_select_v(i, list); } } if (ctx->parent_sess->fids.len) { /* Close all FIDs. */ uint32_t fid; struct _srv_fidinfo *fidinfo __attribute__((unused)); MAP_FOREACH(&ctx->parent_sess->fids, fid, fidinfo) { handle_Tclunk(ctx, &(struct lib9p_msg_Tclunk){.fid = fid}, &(struct lib9p_msg_Rclunk){}); } } /* 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; } 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); ctx->ctx.uid = req->n_uname; ctx->ctx.uname = req->uname.utf8; 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; } srv->auth(&ctx->ctx, req->aname.utf8); lib9p_error(&ctx->ctx.basectx, LINUX_EOPNOTSUPP, "TODO: auth not implemented"); } 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); ctx->ctx.uid = req->n_uname; ctx->ctx.uname = req->uname.utf8; 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 (strcmp(fh->data.auth.uname, req->uname.utf8) != 0) 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, req->uname.utf8); else if (strcmp(fh->data.auth.aname, req->aname.utf8) != 0) 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, 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 implemented"); return; } else { if (req->afid != LIB9P_NOFID) { lib9p_error(&ctx->ctx.basectx, 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.basectx, LINUX_EBADF, "FID already in use"); return; } struct lib9p_srv_file *rootdir = srv->rootdir(&ctx->ctx, req->aname.utf8); assert((rootdir == NULL) == lib9p_ctx_has_error(&ctx->ctx.basectx)); if (lib9p_ctx_has_error(&ctx->ctx.basectx)) return; rootdir->_parent_dir = rootdir; rootdir->_refcount++; if (!fidmap_store(&ctx->parent_sess->fids, req->fid, (struct _srv_fidinfo){ .file = rootdir, .flags = FIDFLAG_ISDIR, })) { lib9p_error(&ctx->ctx.basectx, LINUX_EMFILE, "too many open files"); util_release(&ctx->ctx, rootdir); return; } struct lib9p_stat stat = VCALL(rootdir, stat, &ctx->ctx); if (lib9p_ctx_has_error(&ctx->ctx.basectx)) { handle_Tclunk(ctx, &(struct lib9p_msg_Tclunk){.fid = req->fid}, &(struct lib9p_msg_Rclunk){}); return; } lib9p_assert_stat(stat); assert(stat.file_mode & LIB9P_DM_DIR); resp->qid = stat.file_qid; return; } 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_Twalk(struct _lib9p_srv_req *ctx, struct lib9p_msg_Twalk *req, struct lib9p_msg_Rwalk *resp) { util_handler_common(ctx, req, resp); struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, req->fid); if (!fidinfo) { lib9p_errorf(&ctx->ctx.basectx, LINUX_EBADF, "bad file number %"PRIu32, req->fid); return; } if (req->newfid != req->fid && fidmap_load(&ctx->parent_sess->fids, req->newfid)) { lib9p_error(&ctx->ctx.basectx, LINUX_EBADF, "FID already in use"); return; } struct lib9p_srv_file *dir = fidinfo->file; if (req->newfid != req->fid) { dir = VCALL(dir, clone, &ctx->ctx); assert((dir == NULL) == lib9p_ctx_has_error(&ctx->ctx.basectx)); if (lib9p_ctx_has_error(&ctx->ctx.basectx)) return; dir->_refcount++; /* ref-A: presumptive insertion into fidmap */ } bool isdir = (fidinfo->flags & FIDFLAG_ISDIR); resp->wqid = (struct lib9p_qid *)(&resp[1]); for (resp->nwqid = 0; resp->nwqid < req->nwname; resp->nwqid++) { struct lib9p_srv_file *member; if (strcmp(req->wname[resp->nwqid].utf8, "..") == 0) { member = dir->_parent_dir; } else { if (!isdir) { lib9p_error(&ctx->ctx.basectx, LINUX_ENOTDIR, "not a directory"); break; } member = VCALL(dir, dopen, &ctx->ctx, req->wname[resp->nwqid].utf8); assert((member == NULL) == lib9p_ctx_has_error(&ctx->ctx.basectx)); if (lib9p_ctx_has_error(&ctx->ctx.basectx)) break; member->_parent_dir = dir; dir->_refcount++; /* member->_parent_dir */ } member->_refcount++; /* presumptively take ref-A */ struct lib9p_stat stat = VCALL(member, stat, &ctx->ctx); if (lib9p_ctx_has_error(&ctx->ctx.basectx)) { util_release(&ctx->ctx, member); /* presumption of taking ref-A failed */ break; } lib9p_assert_stat(stat); isdir = stat.file_mode & LIB9P_DM_DIR; if (isdir && !util_check_perm(&ctx->ctx, &stat, 0b001)) { lib9p_error(&ctx->ctx.basectx, LINUX_EACCES, "you do not have execute permission on that directory"); util_release(&ctx->ctx, member); /* presumption of taking ref-A failed */ break; } resp->wqid[resp->nwqid] = stat.file_qid; /* presumption of taking ref-A succeeded */ util_release(&ctx->ctx, dir); dir = member; } if (resp->nwqid == req->nwname) { if (req->newfid == req->fid) { handle_Tclunk(ctx, &(struct lib9p_msg_Tclunk){.fid = req->fid}, &(struct lib9p_msg_Rclunk){}); } if (!fidmap_store(&ctx->parent_sess->fids, req->newfid, (struct _srv_fidinfo){ .file = dir, .flags = isdir ? FIDFLAG_ISDIR : 0, })) { lib9p_error(&ctx->ctx.basectx, LINUX_EMFILE, "too many open files"); util_release(&ctx->ctx, dir); /* presumption of insertion failed */ } } else { assert(lib9p_ctx_has_error(&ctx->ctx.basectx)); if (req->newfid != req->fid) util_release(&ctx->ctx, dir); /* presumption of insertion failed */ if (resp->nwqid > 0) lib9p_ctx_clear_error(&ctx->ctx.basectx); } } 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); /* Check that the FID is valid for this. */ struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, req->fid); if (!fidinfo) { lib9p_errorf(&ctx->ctx.basectx, LINUX_EBADF, "bad file number %"PRIu32, req->fid); return; } if (fidinfo->flags & FIDFLAG_OPEN) { lib9p_error(&ctx->ctx.basectx, LINUX_EALREADY, "FID is already open"); return; } if (fidinfo->flags & FIDFLAG_ISDIR) { if ( ((req->mode & LIB9P_O_MODE_MASK) != LIB9P_O_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; } } /* Variables. */ lib9p_o_t reqmode = req->mode; uint8_t fidflags = fidinfo->flags; struct lib9p_srv_file *file = fidinfo->file; /* Check permissions. */ if (reqmode & LIB9P_O_RCLOSE) { struct lib9p_stat parent_stat = VCALL(file->_parent_dir, stat, &ctx->ctx); if (lib9p_ctx_has_error(&ctx->ctx.basectx)) return; lib9p_assert_stat(parent_stat); if (!util_check_perm(&ctx->ctx, &parent_stat, 0b010)) { lib9p_error(&ctx->ctx.basectx, LINUX_EACCES, "permission denied to remove-on-close"); return; } fidflags = fidflags | FIDFLAG_RCLOSE; } struct lib9p_stat stat = VCALL(file, stat, &ctx->ctx); if (lib9p_ctx_has_error(&ctx->ctx.basectx)) return; lib9p_assert_stat(stat); if (stat.file_mode & LIB9P_QT_APPEND) reqmode = reqmode & ~LIB9P_O_TRUNC; uint8_t perm_bits = 0; switch (reqmode & LIB9P_O_MODE_MASK) { case LIB9P_O_READ: perm_bits = 0b100; fidflags = fidflags | FIDFLAG_OPEN_R; break; case LIB9P_O_WRITE: perm_bits = 0b010; fidflags = fidflags | FIDFLAG_OPEN_W; break; case LIB9P_O_RDWR: perm_bits = 0b110; fidflags = fidflags | FIDFLAG_OPEN_R | FIDFLAG_OPEN_W; break; case LIB9P_O_EXEC: perm_bits = 0b001; fidflags = fidflags | FIDFLAG_OPEN_R; break; } if (!util_check_perm(&ctx->ctx, &stat, perm_bits)) { lib9p_error(&ctx->ctx.basectx, LINUX_EACCES, "permission denied"); } /* Actually make the call. */ uint32_t iounit = VCALL(file, io, &ctx->ctx, reqmode); if (lib9p_ctx_has_error(&ctx->ctx.basectx)) return; /* Success. */ fidinfo->flags = fidflags; resp->qid = stat.file_qid; resp->iounit = iounit; } 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); lib9p_error(&ctx->ctx.basectx, LINUX_EOPNOTSUPP, "create not (yet?) implemented"); } 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); /* Check that the FID is valid for this. */ struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, req->fid); if (!fidinfo) { lib9p_errorf(&ctx->ctx.basectx, LINUX_EBADF, "bad file number %"PRIu32, req->fid); return; } if (!(fidinfo->flags & FIDFLAG_OPEN_R)) { lib9p_error(&ctx->ctx.basectx, LINUX_EINVAL, "FID not open for reading"); return; } /* Variables. */ struct lib9p_srv_file *file = fidinfo->file; resp->data.dat = (char *)(&resp[1]); /* Do it. */ if (fidinfo->flags & FIDFLAG_ISDIR) { /* 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 %"PRIu32"): %"PRIu32, fidinfo->dir_off, req->offset); return; } /* Do it. */ size_t num = VCALL(file, dread, &ctx->ctx, (uint8_t *)resp->data.dat, 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_validate_stat(&ctx->ctx.basectx, req->count, &((uint8_t *)resp->data.dat)[len], &i_len, NULL); len += i_len; } resp->data.len = len; /* Remember. */ fidinfo->dir_idx = idx+num; fidinfo->dir_off = req->offset + len; } else resp->data.len = VCALL(file, pread, &ctx->ctx, resp->data.dat, req->count, req->offset); } 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); /* Check that the FID is valid for this. */ struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, req->fid); if (!fidinfo) { lib9p_errorf(&ctx->ctx.basectx, LINUX_EBADF, "bad file number %"PRIu32, req->fid); return; } if (!(fidinfo->flags & FIDFLAG_OPEN_W)) { lib9p_error(&ctx->ctx.basectx, LINUX_EINVAL, "FID not open for writing"); return; } /* Variables. */ struct lib9p_srv_file *file = fidinfo->file; /* Do it. */ resp->count = VCALL(file, pwrite, &ctx->ctx, req->data.dat, req->data.len, req->offset); } 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); struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, req->fid); if (!fidinfo) { lib9p_errorf(&ctx->ctx.basectx, LINUX_EBADF, "bad file number %"PRIu32, req->fid); return; } if (fidinfo->flags & FIDFLAG_RCLOSE) { handle_Tremove(ctx, &(struct lib9p_msg_Tremove){.fid = req->fid}, &(struct lib9p_msg_Rremove){}); return; } VCALL(fidinfo->file, free, &ctx->ctx); fidmap_del(&ctx->parent_sess->fids, req->fid); } 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); lib9p_error(&ctx->ctx.basectx, LINUX_EOPNOTSUPP, "remove not (yet?) implemented"); } 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); struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, req->fid); if (!fidinfo) { lib9p_errorf(&ctx->ctx.basectx, LINUX_EBADF, "bad file number %"PRIu32, req->fid); return; } resp->stat = VCALL(fidinfo->file, stat, &ctx->ctx); if (!lib9p_ctx_has_error(&ctx->ctx.basectx)) lib9p_assert_stat(resp->stat); } 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); lib9p_error(&ctx->ctx.basectx, LINUX_EOPNOTSUPP, "wstat not (yet?) implemented"); } #ifdef 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); lib9p_error(&ctx->ctx.basectx, LINUX_EOPNOTSUPP, "session not (yet?) implemented"); } 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); lib9p_error(&ctx->ctx.basectx, LINUX_EOPNOTSUPP, "sread not (yet?) implemented"); } 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); lib9p_error(&ctx->ctx.basectx, LINUX_EOPNOTSUPP, "swrite not (yet?) implemented"); } #endif