diff options
Diffstat (limited to 'lib9p/srv.c')
-rw-r--r-- | lib9p/srv.c | 779 |
1 files changed, 488 insertions, 291 deletions
diff --git a/lib9p/srv.c b/lib9p/srv.c index 9192794..60a1bb0 100644 --- a/lib9p/srv.c +++ b/lib9p/srv.c @@ -1,24 +1,54 @@ /* lib9p/srv.c - 9P server * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ -#include <assert.h> #include <alloca.h> #include <inttypes.h> /* for PRI* */ -#include <stdio.h> /* for fprintf(), stderr */ -#include <string.h> /* for strerror() */ +#include <limits.h> /* for SSIZE_MAX, not set by newlib */ +#include <stddef.h> /* for size_t */ +#include <stdlib.h> /* for malloc() */ +#include <string.h> /* for memcpy() */ +#ifndef SSIZE_MAX +#define SSIZE_MAX (SIZE_MAX >> 1) +#endif #include <libcr/coroutine.h> #include <libcr_ipc/chan.h> #include <libcr_ipc/mutex.h> -#include <libcr_ipc/select.h> -#include <libmisc/vcall.h> +#include <libmisc/assert.h> +#include <libmisc/endian.h> +#include <libhw/generic/net.h> + +#define LOG_NAME 9P_SRV +#include <libmisc/log.h> #define IMPLEMENTATION_FOR_LIB9P_SRV_H YES #include <lib9p/srv.h> -#include "internal.h" + +/* config *********************************************************************/ + +#include "config.h" + +#ifndef CONFIG_9P_SRV_MAX_FIDS + #error config.h must define CONFIG_9P_SRV_MAX_FIDS +#endif +#ifndef CONFIG_9P_SRV_MAX_REQS + #error config.h must define CONFIG_9P_SRV_MAX_REQS +#endif +#ifndef CONFIG_9P_SRV_MAX_DEPTH + /* 1=just the root dir, 2=just files in the root dir, 3=1 subdir, ... */ + #error config.h must define CONFIG_9P_SRV_MAX_DEPTH +#endif +#ifndef CONFIG_9P_SRV_MAX_MSG_SIZE + #error config.h must define CONFIG_9P_SRV_MAX_MSG_SIZE +#endif +#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 ********************************************************************/ @@ -37,29 +67,56 @@ int lib9p_srv_acknowledge_flush(struct lib9p_srv_ctx *ctx) { /* structs ********************************************************************/ +typedef typeof( ((struct lib9p_qid){}).path ) srv_path_t; + +struct srv_pathinfo { + lo_interface lib9p_srv_file file; + 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; +}; + +#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" + #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 { - implements_lib9p_srv_file *file; - uint8_t flags; - size_t dir_idx; - uint32_t dir_off; + srv_path_t path; + uint8_t flags; + union { + struct { + lo_interface lib9p_srv_fio io; + } file; + struct { + lo_interface lib9p_srv_dio io; + size_t idx; + uint64_t off; + } dir; + }; }; #define NAME fidmap -#define KEY_T uint32_t +#define KEY_T lib9p_fid_t #define VAL_T struct _srv_fidinfo -#define CAP CONFIG_9P_MAX_FIDS +#define CAP CONFIG_9P_SRV_MAX_FIDS #include "map.h" #define NAME reqmap -#define KEY_T uint32_t +#define KEY_T lib9p_tag_t #define VAL_T struct _lib9p_srv_req * -#define CAP CONFIG_9P_MAX_REQS +#define CAP CONFIG_9P_SRV_MAX_REQS #include "map.h" /* The hierarchy of concepts is: @@ -73,7 +130,7 @@ struct _srv_fidinfo { struct _srv_conn { /* immutable */ struct lib9p_srv *parent_srv; - implements_net_stream_conn *fd; + lo_interface net_stream_conn fd; cid_t reader; /* the lib9p_srv_read_cr() coroutine */ /* mutable */ cr_mutex_t writelock; @@ -88,53 +145,34 @@ struct _srv_sess { /* mutable */ bool initialized; bool closing; - struct reqmap reqs; - struct fidmap fids; + struct pathmap paths; /* srv_path_t => lib9p_srv_file + metadata */ + struct fidmap fids; /* lib9p_fid_t => lib9p_srv_{fio,dio} + metadata */ + struct reqmap reqs; /* lib9p_tag_t => *_lib9p_srv_req */ }; struct _lib9p_srv_req { /* immutable */ struct _srv_sess *parent_sess; uint16_t tag; + uint8_t *net_bytes; /* 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; +#define nonrespond_errorf errorf - 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 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); + cr_mutex_unlock(&req->parent_sess->parent_conn->writelock); + return r; } static void respond_error(struct _lib9p_srv_req *req) { -#ifdef CONFIG_9P_ENABLE_9P2000_u +#if CONFIG_9P_ENABLE_9P2000_u assert(req->ctx.basectx.err_num); #endif assert(req->ctx.basectx.err_msg[0]); @@ -142,12 +180,9 @@ static void respond_error(struct _lib9p_srv_req *req) { 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 + .ename = lib9p_strn(req->ctx.basectx.err_msg, + CONFIG_9P_MAX_ERR_SIZE), +#if CONFIG_9P_ENABLE_9P2000_u .errno = req->ctx.basectx.err_num, #endif }; @@ -155,33 +190,36 @@ static void respond_error(struct _lib9p_srv_req *req) { struct _srv_sess *sess = req->parent_sess; /* Truncate the error-string if necessary to avoid needing to - * return LINUX_ERANGE. The assert() in - * rerror_overhead_for_version() has checked that this - * addition doesn't overflow. */ + * 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; - lib9p_marshal(&req->ctx.basectx, LIB9P_TYP_Rerror, - &host, req->net_bytes); + struct lib9p_Rmsg_send_buf net; + + lib9p_Rmsg_marshal(&req->ctx.basectx, + LIB9P_TYP_Rerror, &host, + &net); - 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); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat" +#pragma GCC diagnostic ignored "-Wformat-extra-args" + infof("< %v", lo_box_lib9p_msg_Rerror_as_fmt_formatter(&host)); +#pragma GCC diagnostic pop + r = write_Rmsg(req, &net); if (r < 0) - nonrespond_errorf("write: %s", strerror(-r)); + nonrespond_errorf("write: %s", net_strerror(-r)); } /* read coroutine *************************************************************/ -static bool read_at_least(implements_net_stream_conn *fd, uint8_t *buf, size_t goal, size_t *done) { +static bool 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 = VCALL(fd, read, &buf[*done], CONFIG_9P_MAX_MSG_SIZE - *done); + ssize_t r = io_read(fd, &buf[*done], goal - *done); if (r < 0) { - nonrespond_errorf("read: %s", strerror(-r)); + nonrespond_errorf("read: %s", net_strerror(-r)); return true; } else if (r == 0) { if (*done != 0) @@ -195,30 +233,34 @@ static bool read_at_least(implements_net_stream_conn *fd, uint8_t *buf, size_t g static void handle_message(struct _lib9p_srv_req *ctx); -__attribute__ ((noreturn)) void lib9p_srv_read_cr(struct lib9p_srv *srv, implements_net_stream_listener *listener) { - uint8_t buf[CONFIG_9P_MAX_MSG_SIZE]; - +[[noreturn]] void lib9p_srv_read_cr(struct lib9p_srv *srv, lo_interface net_stream_listener listener) { assert(srv); assert(srv->rootdir); - assert(listener); + assert(!LO_IS_NULL(listener)); + + srv->readers++; - uint32_t initial_rerror_overhead = rerror_overhead_for_version(0, buf); + uint32_t initial_rerror_overhead = lib9p_version_min_msg_size(LIB9P_VER_unknown); for (;;) { struct _srv_conn conn = { .parent_srv = srv, - .fd = VCALL(listener, accept), + .fd = LO_CALL(listener, accept), .reader = cr_getcid(), }; - if (!conn.fd) { + if (LO_IS_NULL(conn.fd)) { nonrespond_errorf("accept: error"); - continue; + srv->readers--; + if (srv->readers == 0) + while (srv->writers > 0) + _lib9p_srv_reqch_send_req(&srv->_reqch, NULL); + cr_exit(); } struct _srv_sess sess = { .parent_conn = &conn, .version = LIB9P_VER_unknown, - .max_msg_size = CONFIG_9P_MAX_MSG_SIZE, + .max_msg_size = CONFIG_9P_SRV_MAX_MSG_SIZE, .rerror_overhead = initial_rerror_overhead, .initialized = false, }; @@ -226,18 +268,19 @@ __attribute__ ((noreturn)) void lib9p_srv_read_cr(struct lib9p_srv *srv, impleme nextmsg: /* Read the message. */ size_t done = 0; - if (read_at_least(conn.fd, buf, 4, &done)) + uint8_t buf[7]; + if (read_exactly(conn.fd, buf, 4, &done)) goto close; - size_t goal = decode_u32le(buf); + size_t goal = uint32le_decode(buf); if (goal < 7) { nonrespond_errorf("T-message is impossibly small"); goto close; } - if (read_at_least(conn.fd, buf, 7, &done)) + if (read_exactly(conn.fd, buf, 7, &done)) goto close; struct _lib9p_srv_req req = { .parent_sess = &sess, - .tag = decode_u16le(&buf[5]), + .tag = uint16le_decode(&buf[5]), .net_bytes = buf, .ctx = { .basectx = { @@ -255,11 +298,16 @@ __attribute__ ((noreturn)) void lib9p_srv_read_cr(struct lib9p_srv *srv, impleme respond_error(&req); goto nextmsg; } - if (read_at_least(conn.fd, buf, goal, &done)) + 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; + } /* Handle the message... */ - if (buf[4] == LIB9P_TYP_Tversion) + if (req.net_bytes[4] == LIB9P_TYP_Tversion) /* ...in this coroutine for Tversion, */ handle_message(&req); else @@ -267,12 +315,14 @@ __attribute__ ((noreturn)) void lib9p_srv_read_cr(struct lib9p_srv *srv, impleme _lib9p_srv_reqch_send_req(&srv->_reqch, &req); } close: - VCALL(conn.fd, close, true, sess.reqs.len == 0); - if (sess.reqs.len) { + 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); - VCALL(conn.fd, close, true, true); + io_close_write(conn.fd); } } } @@ -280,7 +330,6 @@ __attribute__ ((noreturn)) void lib9p_srv_read_cr(struct lib9p_srv *srv, impleme /* 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; @@ -289,14 +338,19 @@ COROUTINE lib9p_srv_write_cr(void *_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); - /* Deep-copy the request from the reader coroutine's + if (!rpc_handle.req) { + srv->writers--; + _lib9p_srv_reqch_send_resp(rpc_handle, 0); + cr_exit(); + } + /* 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 @@ -334,7 +388,7 @@ _HANDLER_PROTO(clunk); _HANDLER_PROTO(remove); _HANDLER_PROTO(stat); _HANDLER_PROTO(wstat); -#ifdef CONFIG_9P_ENABLE_9P2000_e +#if CONFIG_9P_ENABLE_9P2000_e _HANDLER_PROTO(session); _HANDLER_PROTO(sread); _HANDLER_PROTO(swrite); @@ -356,7 +410,7 @@ static tmessage_handler tmessage_handlers[0x100] = { [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 +#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, @@ -364,28 +418,23 @@ static tmessage_handler tmessage_handlers[0x100] = { }; 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]; + uint8_t *host_req = NULL; + uint8_t host_resp[CONFIG_9P_SRV_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); + ssize_t host_size = lib9p_Tmsg_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, + host_req = calloc(1, host_size); + assert(host_req); + enum lib9p_msg_type typ; + lib9p_Tmsg_unmarshal(&ctx->ctx.basectx, ctx->net_bytes, &typ, host_req); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat" +#pragma GCC diagnostic ignored "-Wformat-extra-args" + infof("> %v", lo_box_lib9p_msg_as_fmt_formatter(&ctx->ctx.basectx, typ, host_req)); +#pragma GCC diagnostic pop /* Handle it. */ tmessage_handlers[typ](ctx, (void *)host_req, (void *)host_resp); @@ -394,15 +443,21 @@ static void handle_message(struct _lib9p_srv_req *ctx) { 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)) + struct lib9p_Rmsg_send_buf net_resp; + if (lib9p_Rmsg_marshal(&ctx->ctx.basectx, + typ+1, host_resp, + &net_resp)) 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); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat" +#pragma GCC diagnostic ignored "-Wformat-extra-args" + infof("< %v", lo_box_lib9p_msg_as_fmt_formatter(&ctx->ctx.basectx, typ+1, &host_resp)); +#pragma GCC diagnostic pop + write_Rmsg(ctx, &net_resp); } + if (host_req) + free(host_req); + free(ctx->net_bytes); } #define util_handler_common(ctx, req, resp) do { \ @@ -412,7 +467,7 @@ static void handle_message(struct _lib9p_srv_req *ctx) { resp->tag = req->tag; \ } while (0) -static inline bool util_check_perm(struct lib9p_srv_ctx *ctx, struct lib9p_stat *stat, uint8_t action) { +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); @@ -423,17 +478,107 @@ static inline bool util_check_perm(struct lib9p_srv_ctx *ctx, struct lib9p_stat return mode & action; } -static inline bool util_release(struct lib9p_srv_ctx *ctx, implements_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); +/** + * Ensures that `file` is saved into the pathmap, and increments the + * gc_refcount by 1 (for presumptive insertion into the fidmap). + * parent_path's gc_refcount is also incremented as appropriate. + * + * Returns a pointer to the stored pathinfo. + */ +static inline struct srv_pathinfo *srv_util_pathsave(struct _lib9p_srv_req *ctx, + lo_interface lib9p_srv_file file, + srv_path_t parent_path) { + assert(ctx); + assert(!LO_IS_NULL(file)); + + struct lib9p_qid qid = LO_CALL(file, qid); + struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, qid.path); + if (pathinfo) + assert(LO_EQ(pathinfo->file, file)); + else { + pathinfo = pathmap_store(&ctx->parent_sess->paths, qid.path, + (struct srv_pathinfo){ + .file = file, + .parent_dir = parent_path, + .gc_refcount = 0, + .io_refcount = 0, + }); + assert(pathinfo); + if (parent_path != qid.path) { + struct srv_pathinfo *parent = pathmap_load(&ctx->parent_sess->paths, parent_path); + assert(parent); + parent->gc_refcount++; + } } - return lib9p_ctx_has_error(&ctx->basectx); + pathinfo->gc_refcount++; + return pathinfo; } +/** + * Decrement the path's gc_refcount, and trigger garbage collection as + * appropriate. + */ +static inline void srv_util_pathfree(struct _lib9p_srv_req *ctx, srv_path_t path) { + assert(ctx); + + struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, path); + assert(pathinfo); + pathinfo->gc_refcount--; + if (pathinfo->gc_refcount == 0) { + if (pathinfo->parent_dir != path) + srv_util_pathfree(ctx, pathinfo->parent_dir); + LO_CALL(pathinfo->file, free); + pathmap_del(&ctx->parent_sess->paths, path); + } +} + +static inline bool srv_util_pathisdir(struct srv_pathinfo *pathinfo) { + assert(pathinfo); + return LO_CALL(pathinfo->file, qid).type & LIB9P_QT_DIR; +} + +/** + * Store fid as pointing to pathinfo. Assumes that + * pathinfo->gc_refcount has already been incremented; does *not* + * decrement it on failure. + */ +static inline struct _srv_fidinfo *srv_util_fidsave(struct _lib9p_srv_req *ctx, lib9p_fid_t fid, struct srv_pathinfo *pathinfo, bool overwrite) { + assert(ctx); + assert(fid != LIB9P_FID_NOFID); + assert(pathinfo); + + struct lib9p_qid qid = LO_CALL(pathinfo->file, qid); + + struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, fid); + if (fidinfo) { + if (overwrite) { + struct srv_pathinfo *old_pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path); + assert(old_pathinfo); + if (srv_util_pathisdir(old_pathinfo)) + LO_CALL(fidinfo->dir.io, iofree); + else + LO_CALL(fidinfo->file.io, iofree); + srv_util_pathfree(ctx, fidinfo->path); + } else { + lib9p_error(&ctx->ctx.basectx, + LINUX_EBADF, "FID already in use"); + return NULL; + } + } 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; + } + } + *fidinfo = (struct _srv_fidinfo){ + .path = qid.path, + }; + return fidinfo; +} + + static void handle_Tversion(struct _lib9p_srv_req *ctx, struct lib9p_msg_Tversion *req, struct lib9p_msg_Rversion *resp) { @@ -448,33 +593,29 @@ static void handle_Tversion(struct _lib9p_srv_req *ctx, '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] == '.')) { + (req->version.len == 6 || req->version.utf8[6] == '.')) { version = LIB9P_VER_9P2000; -#ifdef CONFIG_9P_ENABLE_9P2000_u - if (strcmp(&req->version.utf8[6], ".u") == 0) +#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 -#ifdef CONFIG_9P_ENABLE_9P2000_e - if (strcmp(&req->version.utf8[6], ".e") == 0) +#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 = rerror_overhead_for_version(version, ctx->net_bytes); + uint32_t min_msg_size = lib9p_version_min_msg_size(version); if (req->max_msg_size < min_msg_size) { lib9p_errorf(&ctx->ctx.basectx, LINUX_EDOM, "requested max_msg_size is less than minimum for %s (%"PRIu32" < %"PRIu32")", - version, req->max_msg_size, min_msg_size); + lib9p_version_str(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 + resp->version = lib9p_str((char *)lib9p_version_str(version)); /* cast to discard "const" qualifier */ + resp->max_msg_size = (CONFIG_9P_SRV_MAX_MSG_SIZE < req->max_msg_size) + ? CONFIG_9P_SRV_MAX_MSG_SIZE : req->max_msg_size; /* Close the old session. */ @@ -483,7 +624,7 @@ static void handle_Tversion(struct _lib9p_srv_req *ctx, * 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)); + uint16_t tag [[gnu::unused]]; struct _lib9p_srv_req **reqpp; size_t i = 0; bool flushed; @@ -496,7 +637,7 @@ static void handle_Tversion(struct _lib9p_srv_req *ctx, if (ctx->parent_sess->fids.len) { /* Close all FIDs. */ uint32_t fid; - struct _srv_fidinfo *fidinfo __attribute__((unused)); + struct _srv_fidinfo *fidinfo [[gnu::unused]]; MAP_FOREACH(&ctx->parent_sess->fids, fid, fidinfo) { handle_Tclunk(ctx, &(struct lib9p_msg_Tclunk){.fid = fid}, @@ -515,8 +656,8 @@ static void handle_Tauth(struct _lib9p_srv_req *ctx, struct lib9p_msg_Rauth *resp) { util_handler_common(ctx, req, resp); - ctx->ctx.uid = req->n_uname; - ctx->ctx.uname = req->uname.utf8; + 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) { @@ -525,7 +666,7 @@ static void handle_Tauth(struct _lib9p_srv_req *ctx, return; } - srv->auth(&ctx->ctx, req->aname.utf8); + srv->auth(&ctx->ctx, req->aname); lib9p_error(&ctx->ctx.basectx, LINUX_EOPNOTSUPP, "TODO: auth not implemented"); } @@ -535,8 +676,8 @@ static void handle_Tattach(struct _lib9p_srv_req *ctx, struct lib9p_msg_Rattach *resp) { util_handler_common(ctx, req, resp); - ctx->ctx.uid = req->n_uname; - ctx->ctx.uname = req->uname.utf8; + 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) { @@ -548,14 +689,16 @@ static void handle_Tattach(struct _lib9p_srv_req *ctx, 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) + 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, req->uname.utf8); - else if (strcmp(fh->data.auth.aname, req->aname.utf8) != 0) + 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, req->aname.utf8); + 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"); @@ -564,51 +707,41 @@ static void handle_Tattach(struct _lib9p_srv_req *ctx, return; */ lib9p_error(&ctx->ctx.basectx, - LINUX_EOPNOTSUPP, "TODO: auth not implemented"); + LINUX_EOPNOTSUPP, "TODO: auth not (yet?) implemented"); return; } else { - if (req->afid != LIB9P_NOFID) { + 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; } } - if (fidmap_load(&ctx->parent_sess->fids, req->fid)) { - lib9p_error(&ctx->ctx.basectx, - LINUX_EBADF, "FID already in use"); - return; + if (req->fid == LIB9P_FID_NOFID) { + lib9p_error(&ctx->ctx.basectx, + LINUX_EBADF, "cannot assign to NOFID"); + return; } - implements_lib9p_srv_file *rootdir = srv->rootdir(&ctx->ctx, req->aname.utf8); - assert((rootdir == NULL) == lib9p_ctx_has_error(&ctx->ctx.basectx)); + /* 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; - 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_qid root_qid = LO_CALL(root_file, qid); + assert(root_qid.type & LIB9P_QT_DIR); + + /* 2. pathinfo */ + struct srv_pathinfo *root_pathinfo = srv_util_pathsave(ctx, root_file, root_qid.path); - 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){}); + /* 3. fidinfo */ + if (!srv_util_fidsave(ctx, req->fid, root_pathinfo, false)) { + srv_util_pathfree(ctx, root_qid.path); return; } - lib9p_assert_stat(stat); - assert(stat.file_mode & LIB9P_DM_DIR); - - resp->qid = stat.file_qid; + resp->qid = root_qid; return; } @@ -627,87 +760,75 @@ static void handle_Twalk(struct _lib9p_srv_req *ctx, struct lib9p_msg_Rwalk *resp) { util_handler_common(ctx, req, resp); + if (req->newfid == LIB9P_FID_NOFID) { + lib9p_error(&ctx->ctx.basectx, + LINUX_EBADF, "cannot assign to NOFID"); + return; + } + 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; - } - implements_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); + struct srv_pathinfo *pathinfo = pathmap_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++) { - implements_lib9p_srv_file *member; - if (strcmp(req->wname[resp->nwqid].utf8, "..") == 0) { - member = dir->_parent_dir; + 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); + assert(new_pathinfo); + new_pathinfo->gc_refcount++; } else { - if (!isdir) { + if (!srv_util_pathisdir(pathinfo)) { 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)); + lo_interface lib9p_srv_file member_file = LO_CALL(pathinfo->file, dwalk, &ctx->ctx, req->wname[resp->nwqid]); + assert(LO_IS_NULL(member_file) == lib9p_ctx_has_error(&ctx->ctx.basectx)); if (lib9p_ctx_has_error(&ctx->ctx.basectx)) break; - member->_parent_dir = dir; - dir->_refcount++; /* member->_parent_dir */ + new_pathinfo = srv_util_pathsave(ctx, member_file, LO_CALL(pathinfo->file, qid).path); } - 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; + 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)) + 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); + break; + } } - resp->wqid[resp->nwqid] = stat.file_qid; + resp->wqid[resp->nwqid] = LO_CALL(new_pathinfo->file, qid); - /* presumption of taking ref-A succeeded */ - util_release(&ctx->ctx, dir); - dir = member; + srv_util_pathfree(ctx, LO_CALL(pathinfo->file, qid).path); + pathinfo = new_pathinfo; } 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 */ + if (srv_util_pathisdir(pathinfo)) + LO_CALL(fidinfo->dir.io, iofree); + else + LO_CALL(fidinfo->file.io, iofree); + fidinfo->flags = 0; } + if (!srv_util_fidsave(ctx, req->newfid, pathinfo, req->newfid == req->fid)) + srv_util_pathfree(ctx, LO_CALL(pathinfo->file, qid).path); } else { assert(lib9p_ctx_has_error(&ctx->ctx.basectx)); - if (req->newfid != req->fid) - util_release(&ctx->ctx, dir); /* presumption of insertion failed */ + srv_util_pathfree(ctx, LO_CALL(pathinfo->file, qid).path); if (resp->nwqid > 0) lib9p_ctx_clear_error(&ctx->ctx.basectx); } @@ -730,8 +851,10 @@ static void handle_Topen(struct _lib9p_srv_req *ctx, LINUX_EALREADY, "FID is already open"); return; } - if (fidinfo->flags & FIDFLAG_ISDIR) { - if ( ((req->mode & LIB9P_O_MODE_MASK) != LIB9P_O_READ) || + 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_MODE_READ) || (req->mode & LIB9P_O_TRUNC) || (req->mode & LIB9P_O_RCLOSE) ) { lib9p_error(&ctx->ctx.basectx, @@ -741,61 +864,92 @@ static void handle_Topen(struct _lib9p_srv_req *ctx, } /* Variables. */ - lib9p_o_t reqmode = req->mode; - uint8_t fidflags = fidinfo->flags; - implements_lib9p_srv_file *file = fidinfo->file; + lib9p_o_t reqmode = req->mode; + uint8_t fidflags = fidinfo->flags; /* Check permissions. */ if (reqmode & LIB9P_O_RCLOSE) { - struct lib9p_stat parent_stat = VCALL(file->_parent_dir, stat, &ctx->ctx); + 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 (lib9p_ctx_has_error(&ctx->ctx.basectx)) return; - lib9p_assert_stat(parent_stat); - if (!util_check_perm(&ctx->ctx, &parent_stat, 0b010)) { + 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; } - fidflags = fidflags | FIDFLAG_RCLOSE; + fidflags |= FIDFLAG_RCLOSE; } - struct lib9p_stat stat = VCALL(file, stat, &ctx->ctx); + struct lib9p_stat stat = LO_CALL(pathinfo->file, stat, &ctx->ctx); if (lib9p_ctx_has_error(&ctx->ctx.basectx)) return; - lib9p_assert_stat(stat); - if (stat.file_mode & LIB9P_QT_APPEND) + lib9p_stat_assert(stat); + if ((stat.file_mode & LIB9P_DM_EXCL) && pathinfo->io_refcount) { + lib9p_error(&ctx->ctx.basectx, + LINUX_EEXIST, "exclusive file is already opened"); + return; + } + if (stat.file_mode & LIB9P_DM_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; - fidflags = fidflags | FIDFLAG_OPEN_R; + rd = true; break; - case LIB9P_O_WRITE: + case LIB9P_O_MODE_WRITE: perm_bits = 0b010; - fidflags = fidflags | FIDFLAG_OPEN_W; + wr = true; break; - case LIB9P_O_RDWR: + case LIB9P_O_MODE_RDWR: perm_bits = 0b110; - fidflags = fidflags | FIDFLAG_OPEN_R | FIDFLAG_OPEN_W; + rd = wr = true; break; - case LIB9P_O_EXEC: + case LIB9P_O_MODE_EXEC: perm_bits = 0b001; - fidflags = fidflags | FIDFLAG_OPEN_R; + rd = true; break; } - if (!util_check_perm(&ctx->ctx, &stat, perm_bits)) { + if (!srv_util_check_perm(ctx, &stat, perm_bits)) { lib9p_error(&ctx->ctx.basectx, LINUX_EACCES, "permission denied"); + return; } /* Actually make the call. */ - uint32_t iounit = VCALL(file, io, &ctx->ctx, reqmode); - if (lib9p_ctx_has_error(&ctx->ctx.basectx)) - return; + uint32_t iounit; + struct lib9p_qid qid; + if (srv_util_pathisdir(pathinfo)) { + fidinfo->dir.io = LO_CALL(pathinfo->file, dopen, &ctx->ctx); + assert(LO_IS_NULL(fidinfo->dir.io) == lib9p_ctx_has_error(&ctx->ctx.basectx)); + if (lib9p_ctx_has_error(&ctx->ctx.basectx)) + return; + fidinfo->dir.idx = 0; + fidinfo->dir.off = 0; + qid = LO_CALL(fidinfo->dir.io, qid); + iounit = 0; + } else { + fidinfo->file.io = LO_CALL(pathinfo->file, fopen, &ctx->ctx, + rd, wr, + reqmode & LIB9P_O_TRUNC); + assert(LO_IS_NULL(fidinfo->file.io) == lib9p_ctx_has_error(&ctx->ctx.basectx)); + if (lib9p_ctx_has_error(&ctx->ctx.basectx)) + return; + qid = LO_CALL(fidinfo->file.io, qid); + iounit = LO_CALL(fidinfo->file.io, iounit); + } /* Success. */ + if (rd) + fidflags |= FIDFLAG_OPEN_R; + if (wr) + fidflags |= FIDFLAG_OPEN_W; + pathinfo->io_refcount++; fidinfo->flags = fidflags; - resp->qid = stat.file_qid; + resp->qid = qid; resp->iounit = iounit; } @@ -813,6 +967,8 @@ static void handle_Tread(struct _lib9p_srv_req *ctx, struct lib9p_msg_Rread *resp) { util_handler_common(ctx, req, resp); + /* TODO: serialize simultaneous reads to the same FID */ + /* Check that the FID is valid for this. */ struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, req->fid); if (!fidinfo) { @@ -827,38 +983,47 @@ static void handle_Tread(struct _lib9p_srv_req *ctx, } /* Variables. */ - implements_lib9p_srv_file *file = fidinfo->file; - resp->data.dat = (char *)(&resp[1]); + struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path); + assert(pathinfo); /* Do it. */ - if (fidinfo->flags & FIDFLAG_ISDIR) { + 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 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); + LINUX_EINVAL, "invalid offset (must be 0 or %"PRIu64"): %"PRIu64, + fidinfo->dir.off, req->offset); return; } /* Do it. */ - size_t num = VCALL(file, dread, &ctx->ctx, (uint8_t *)resp->data.dat, req->count, idx); + resp->data = (char *)(&resp[1]); + size_t num = LO_CALL(fidinfo->dir.io, dread, &ctx->ctx, (uint8_t *)resp->data, req->count, idx); /* Translate object-count back to byte-count. */ uint32_t len = 0; for (size_t i = 0; i < num; i++) { uint32_t i_len; - lib9p_validate_stat(&ctx->ctx.basectx, req->count, &((uint8_t *)resp->data.dat)[len], &i_len, NULL); + lib9p_stat_validate(&ctx->ctx.basectx, req->count, &((uint8_t *)resp->data)[len], &i_len, NULL); len += i_len; } - resp->data.len = len; + resp->count = 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); + fidinfo->dir.idx = idx+num; + fidinfo->dir.off = req->offset + len; + } else { + struct iovec iov; + LO_CALL(fidinfo->file.io, pread, &ctx->ctx, req->count, req->offset, &iov); + if (!lib9p_ctx_has_error(&ctx->ctx.basectx)) { + resp->count = iov.iov_len; + resp->data = iov.iov_base; + if (resp->count > req->count) + resp->count = req->count; + } + } } static void handle_Twrite(struct _lib9p_srv_req *ctx, @@ -866,6 +1031,8 @@ static void handle_Twrite(struct _lib9p_srv_req *ctx, 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); if (!fidinfo) { @@ -880,41 +1047,69 @@ static void handle_Twrite(struct _lib9p_srv_req *ctx, } /* Variables. */ - implements_lib9p_srv_file *file = fidinfo->file; + struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path); + assert(pathinfo); /* Do it. */ - resp->count = VCALL(file, pwrite, &ctx->ctx, req->data.dat, req->data.len, req->offset); + resp->count = LO_CALL(fidinfo->file.io, pwrite, &ctx->ctx, req->data, req->count, 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); +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); if (!fidinfo) { lib9p_errorf(&ctx->ctx.basectx, - LINUX_EBADF, "bad file number %"PRIu32, req->fid); + LINUX_EBADF, "bad file number %"PRIu32, fid); return; } - if (fidinfo->flags & FIDFLAG_RCLOSE) { - handle_Tremove(ctx, - &(struct lib9p_msg_Tremove){.fid = req->fid}, - &(struct lib9p_msg_Rremove){}); - 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: + if (fidinfo->flags & FIDFLAG_OPEN) { + if (srv_util_pathisdir(pathinfo)) + LO_CALL(fidinfo->dir.io, iofree); + else + LO_CALL(fidinfo->file.io, iofree); + pathinfo->io_refcount--; } + srv_util_pathfree(ctx, LO_CALL(pathinfo->file, qid).path); + fidmap_del(&ctx->parent_sess->fids, fid); +} - VCALL(fidinfo->file, free, &ctx->ctx); - fidmap_del(&ctx->parent_sess->fids, req->fid); +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 _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"); + clunkremove(ctx, req->fid, true); } static void handle_Tstat(struct _lib9p_srv_req *ctx, @@ -928,10 +1123,12 @@ static void handle_Tstat(struct _lib9p_srv_req *ctx, LINUX_EBADF, "bad file number %"PRIu32, req->fid); return; } + struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path); + assert(pathinfo); - resp->stat = VCALL(fidinfo->file, stat, &ctx->ctx); + resp->stat = LO_CALL(pathinfo->file, stat, &ctx->ctx); if (!lib9p_ctx_has_error(&ctx->ctx.basectx)) - lib9p_assert_stat(resp->stat); + lib9p_stat_assert(resp->stat); } static void handle_Twstat(struct _lib9p_srv_req *ctx, @@ -943,7 +1140,7 @@ static void handle_Twstat(struct _lib9p_srv_req *ctx, LINUX_EOPNOTSUPP, "wstat not (yet?) implemented"); } -#ifdef CONFIG_9P_ENABLE_9P2000_e +#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) { |