diff options
-rw-r--r-- | 9p/9P2000.u.txt | 2 | ||||
-rw-r--r-- | 9p/defs.h | 9 | ||||
-rw-r--r-- | 9p/internal.h | 47 | ||||
-rw-r--r-- | 9p/srv.c | 205 | ||||
-rw-r--r-- | 9p/srv.h | 25 | ||||
-rw-r--r-- | Makefile | 3 | ||||
-rw-r--r-- | README.md | 14 | ||||
-rw-r--r-- | config.h | 58 | ||||
-rw-r--r-- | coroutine.c | 63 | ||||
-rw-r--r-- | coroutine.h | 8 | ||||
-rw-r--r-- | netio.h | 13 | ||||
-rw-r--r-- | netio_posix.c | 84 |
12 files changed, 388 insertions, 143 deletions
diff --git a/9p/9P2000.u.txt b/9p/9P2000.u.txt index 20aea48..60d2b11 100644 --- a/9p/9P2000.u.txt +++ b/9p/9P2000.u.txt @@ -15,6 +15,6 @@ stat += "file_extension[s]" "file_owner_n_gid[4]" "file_last_modified_n_uid[4]" -Tauth += "nxs_uname[4]" +Tauth += "n_uname[4]" Rerror += "errno[4]" @@ -13,6 +13,7 @@ #define P9_NOFID ((uint32_t)~0U) enum p9_version { + P9_VER_UNINITIALIZED, /* P9_VER_9P1, */ P9_VER_9P2000, /*P9_VER_9P2000_u,*/ @@ -21,13 +22,9 @@ enum p9_version { _P9_VER_CNT, }; -struct p9_ctx { - enum p9_version version; - uint32_t max_msg_size; +struct p9_ctx; - uint32_t err_num; - char err_msg[256]; /* I chose 256 arbitrarily. */ -}; +enum p9_version p9_ctx_version(p9_ctx *); /** Write an static error into ctx, return -1. */ int p9_error(struct p9_ctx *ctx, uint32_t linux_errno, char const *msg); diff --git a/9p/internal.h b/9p/internal.h index 1bc0e92..61977d4 100644 --- a/9p/internal.h +++ b/9p/internal.h @@ -7,15 +7,36 @@ #ifndef _9P_INTERNAL_H_ #define _9P_INTERNAL_H_ +#include <assert.h> #include <stdint.h> #include <stdbool.h> #include "9p/defs.h" +#define USE_CONFIG_9P +#include "config.h" +static_assert(CONFIG_9P_MAX_ERR_SIZE <= UINT16_MAX); + /* C language *****************************************************************/ #define UNUSED(name) /* name __attribute__ ((unused)) */ +/* types **********************************************************************/ + +struct p9_ctx { + enum p9_version version; + uint32_t max_msg_size; + uint32_t Rerror_overhead; + + uint32_t err_num; + char err_msg[CONFIG_9P_MAX_ERR_SIZE]; +}; + +static inline enum p9_version p9_ctx_version(p9_ctx *ctx) { + assert(ctx); + return ctx->version; +} + /* vtables ********************************************************************/ typedef bool (*_checksize_fn_t)(uint32_t net_len, uint8_t *net_bytes, uint32_t *mut_net_offset, size_t *mut_host_extra); @@ -65,26 +86,30 @@ static inline uint64_t decode_u64le(uint8_t *in) { ; } -static inline bool is_valid_utf8_without_nul(uint8_t *str, size_t len) { - uint8_t mask; +static inline bool _is_valid_utf8(uint8_t *str, size_t len, bool forbid_nul) { + uint32_t ch; uint8_t chlen; + assert(str); for (size_t pos = 0; pos < len;) { - if ((str[pos] & 0b10000000) == 0b00000000) { mask = 0b01111111; chlen = 1; } - else if ((str[pos] & 0b11100000) == 0b11000000) { mask = 0b00011111; chlen = 2; } - else if ((str[pos] & 0b11110000) == 0b11100000) { mask = 0b00001111; chlen = 3; } - else if ((str[pos] & 0b11111000) == 0b11110000) { mask = 0b00000111; chlen = 4; } + if ((str[pos] & 0b10000000) == 0b00000000) { ch = str[pos] & 0b01111111; chlen = 1; } + else if ((str[pos] & 0b11100000) == 0b11000000) { ch = str[pos] & 0b00011111; chlen = 2; } + else if ((str[pos] & 0b11110000) == 0b11100000) { ch = str[pos] & 0b00001111; chlen = 3; } + else if ((str[pos] & 0b11111000) == 0b11110000) { ch = str[pos] & 0b00000111; chlen = 4; } else return false; - if (pos + chlen > len || (str[pos] & mask) == 0) return false; - switch (chlen) { - case 4: if ((str[pos+3] & 0b11000000) != 0b10000000) return false; /* fallthrough */ - case 3: if ((str[pos+2] & 0b11000000) != 0b10000000) return false; /* fallthrough */ - case 2: if ((str[pos+1] & 0b11000000) != 0b10000000) return false; /* fallthrough */ + if ((ch == 0 && (chlen != 1 || forbid_nul)) || pos + chlen > len) return false; + for (uint8_t i = 1; i < chlen; i++) { + if ((str[pos+i] & 0b11000000) != 0b10000000) return false; + ch = (ch << 6) | (str[pos+i] & 0b00111111); } + if (ch > 0x10FFFF) return false; pos += chlen; } return true; } +#define is_valid_utf8(str, len) _is_valid_utf8(str, len, false) +#define is_valid_utf8_without_nul(str, len) _is_valid_utf8(str, len, true) + /* marshal utilities **********************************************************/ static inline void encode_u8le(uint8_t in, uint8_t *out) { @@ -1,44 +1,191 @@ -#include "net9p.h" -#include "net9p_defs.h" +#include <assert.h> + +#include "coroutine.h" #include "netio.h" +#include "9p/9p.h" -#define MAX_MSG_SIZE 1024 +#include "9p/internal.h" -int read_msg(int conn, uint16_t *out_tag, void **out_body) { - uint8_t buf[MAX_MSG_SIZE]; - size_t todo = 7, done = 0; +struct p9_srvconn { + /* immutable */ + p9_srv *srv; + cid_t reader; + int fd; + /* mutable */ + uint32_t max_msg_size; + enum p9_version version; + unsigned int refcount; +}; - while (done < todo) { - ssize_t r = netio_read(conn, buf, 7); - if (r < 0) - return r; - done += r; - } - todo = docode_u32le(buf); - if (todo < 7) - return -EINVAL; - while (done < todo) { - ssize_t r = netio_read(conn, buf, 7); - if (r < 0) - return r; - done += r; - } - return v9fs_unmarshal_msg(buf, out_tag, out_body); -} +struct p9_srvreq { + p9_srvconn *conn; + uint8_t *msg; +}; + +COROUTINE p9_srv_read_cr(void *_srv) { + uint8_t buf[CONFIG_9P_MAX_MSG_SIZE]; -void net9p_cr(void *_arg) { - int sock = *((int *)_arg); + p9_srv *srv = _srv; + assert(srv); cr_begin(); for (;;) { - int conn = netio_accept(sock); - if (conn < 0) { - error(0, -conn, "netio_accept"); + struct p9_srvconn conn = { + .srv = srv, + .reader = cr_getcid(); + + .max_msg_size = CONFIG_9P_MAX_MSG_SIZE; + .version = P9_VER_UNINITIALIZED; + .refcount = 1, + }; + conn.fd = netio_accept(srv->sockfd); + if (conn.fd < 0) { + fprintf(stderr, "error: accept: %m", -conn.fd); continue; } - + for (;;) { + /* Read the message size. */ + size_t goal = 4, done = 0; + while (done < goal) { + ssize_t r = netio_read(conn.fd, &buf[done], sizeof(buf)-done); + if (r < 0) { + fprintf(stderr, "error: read: %m", -r); + goto close; + } else if (r == 0) { + if (done != 0) + fprintf(stderr, "error: read: unexpected EOF"); + goto close; + } + done += r; + } + goal = decode_u32le(buf); + if (goal < 7) { + /* We can't respond with an Rerror becuase we wouldn't know what tag to use! */ + fprintf(stderr, "error: T-message is impossibly small"); + goto close; + } + if (goal > conn.max_msg_size) { + struct p9_ctx ctx = { + .version = conn.version, + .max_msg_size = conn.max_msg_size, + }; + if (initialized) + p9_errorf(&ctx, LINUX_EMSGSIZE, "T-message larger than negotiated limit (%zu > %zu)", goal, conn.max_msg_size); + else + p9_errorf(&ctx, LINUX_EMSGSIZE, "T-message larger than server limit (%zu > %zu)", goal, conn.max_msg_size); + marshal_error(&ctx, buf); + netio_write(conn.fd, buf, decode_u32le(buf)); + continue; + } + /* Read the rest of the message. */ + while (done < goal) { + ssize_t r = netio_read(conn.fd, &buf[done], sizeof(buf)-done); + if (r < 0) { + fprintf(stderr, "error: read: %m", -r); + goto close; + } else if (r == 0) { + fprintf(stderr, "error: read: unexpected EOF"); + goto close; + } + done += r; + } + + /* Handle the message... */ + if (conn.version == P9_VER_UNINITIALIZED) { + /* ...synchronously if we haven't negotiated the protocol yet, ... */ + handle_message(&conn, buf); + } else { + /* ...asynchronously if we have. */ + cr_chan_send(&srv->reqch, buf); + cr_pause_and_yield(); + } + } + close: + netio_close(conn.fd, true, (--conn.refcount) == 0); + if (conn.refcount) { + cr_pause_and_yield(); + assert(conn.refcount == 0); + netio_close(conn.fd, false, true); + } } cr_end(); } + +COROUTINE p9_srv_write_cr(void *_srv) { + uint8_t net[CONFIG_9P_MAX_MSG_SIZE]; + + p9_srv *srv = _srv; + assert(srv); + cr_begin(); + + for (;;) { + struct p9_srvreq req; + cr_chan_recv(&srv->reqch, &req); + memcpy(net, req.msg, decode_u32le(req.msg)); + req.conn->refcount++; + cr_unpause(req.conn->reader); + + handle_message(&req.conn, net); + + if ((--req.conn->refcount) == 0) + cr_unpause(req.conn->reader); + } + + cr_end(); +} + +void handle_message(p9_srvconn *conn, uint8_t *net) { + uint8_t host[CONFIG_9P_MAX_MSG_SIZE]; + + struct p9_ctx ctx = { + .version = req.conn->version, + .max_msg_size = req.conn->max_msg_size, + }; + + size_t host_size = p9_unmarshal_size(&ctx, net); + if (host_size == (size_t)-1) + goto write; + if (host_size > sizeof(host)) { + p9_errorf(&ctx, LINUX_EMSGSIZE, "unmarshalled payload larger than server limit (%zu > %zu)", host_size, sizeof(host)); + goto write; + } + + uint16_t tag; + uint8_t typ = p9_unmarshal(&ctx, net, &tag, host); + if (typ == (uint8_t)-1) + goto write; + if (typ % 2 != 0) { + p9_errorf(&ctx, LINUX_EOPNOTSUPP, "expected a T-message but got an R-message"); + goto write; + } + + TODO; + + write: + if (ctx.err_num || ctx.err_msg[0]) + marshal_error(&ctx, net); + else + TODO; + netio_write(req.conn->fd, net, decode_u32le(net)); +} + +static inline uint16_t min_u16(uint16_t a, b) { + return (a < b) ? a : b; +} + +/* We have special code for marshaling Rerror because we don't ever + * want to produce an error because the err_msg is too long for the + * `ctx->max_msg_size`! */ +void marshal_error(struct p9_ctx *ctx, uint16_t tag, uint8_t *net) { + struct p9_msg_Rerror host = { + .ename = { + .len = strnlen(ctx->err_msg, CONFIG_9P_MAX_ERR_SIZE), + .utf8 = ctx->err_msg, + }, + }; + if (host.ename.len + ctx->Rerror_overhead > ctx->max_msg_size) + host.ename.len = ctx->max_msg_size - overhead; + p9_marshal(ctx, tag, host, net); +} @@ -3,25 +3,14 @@ #include "coroutine.h" -#define 9P_DEFAULT_PORT 564 +struct p9_srvreq; -/** - * The default here is the same as in Plan 9 4e's lib9p. It's sized - * so that a Twrite message can return 8KiB of data; it uses the - * default (1024*8)+24 with the comment that "24" is "ample room for - * Twrite/Rread header (iounit)". In fact, the Twrite header is only - * 23 bytes ("size[4] Twrite[1] tag[2] fid[4] offset[8] count[4]") and - * the Rread header is even shorter at 11 bytes ("size[4] Rread[1] - * tag[2] count[4]"), so "24" appears to be the size of the Twrite - * header rounded up to a nice round number. - * - * In older versions of 9P ("9P1"), the max message size was defined - * as part of the protocol specification rather than negotiated. In - * Plan 9 1e it was (1024*8)+128, and was bumped to (1024*8)+160 in 2e - * and 3e. - */ -#define 9P_DEFAULT_MAX_MSG_SIZE ((1024*8)+24) +struct p9_srv { + int sockfd; + cr_chan_t(p9_srvreq *) reqch; +}; -COROUTINE net9p_cr(void *); +COROUTINE p9_srv_read_cr(void *_srv); +COROUTINE p9_srv_write_cr(void *_srv); #endif /* _NET9P_H_ */ @@ -14,7 +14,8 @@ linux.git = $(HOME)/src/github.com/torvalds/linux 9p/defs-%.c 9p/defs-%.h: 9p/defs.gen 9p/%.txt $^ -srv9p: srv9p.o coroutine.o net9p.o 9p/9P2000.o +lib9p = 9p/defs.o 9p/defs-9P2000.o 9p/srv.o +srv9p: srv9p.o coroutine.o netio_posix.o $(lib9p) sources_py = 9p/defs.gen sources_py += 9p/linux-errno.h.gen @@ -39,9 +39,9 @@ protocol over TCP: - `9P2000` (base protocol): Yes - `9P2000.u` (Unix extension): Yes, with Linux kernel architecture-"generic" errnos. This will match the Linux kernel - errnos on most architectures (but notably not on Alpha, MIPS, - PA-RISC, PowerPC, or SPARC; I am unsure whether on these - platforms the kernel's v9fs filesystem driver will map the + errnos on most architectures (but, as of Linux v6.7, not on + Alpha, MIPS, PA-RISC, PowerPC, or SPARC; I am unsure whether on + these platforms the kernel's v9fs filesystem driver will map the "generic" errnos to the architecture-specific errnos for you). - `9P2000.L` (Linux extension): No, it's an abomination and unlikely to ever be supported @@ -72,6 +72,10 @@ Some notes on choosing a client: # Bugs/Limitations - Only supports 8 concurrent TCP connectsions to the 9P server (due - to limitations in the W5500 TCP-offload chip) + to limitations in the W5500 TCP-offload chip; TODO: investigate + using a software TCP/IP stack with the W5500 in MAC-raw mode) + - Only supports 16 concurrent 9P requests (I wanted a static limit, + and "connections*2" seemed reasonable) - Only supports IPv4, not IPv6 (due to limitations in the W5500 - TCP-offload chip) + TCP-offload chip; TODO: investigate upgrading to the W6100 or using + a software TCP/IP stack with the W5500 in MAC-raw mode) diff --git a/config.h b/config.h new file mode 100644 index 0000000..37bfcbe --- /dev/null +++ b/config.h @@ -0,0 +1,58 @@ +/* config.h - Compile-time configuration for sbc-harness + * + * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-Licence-Identifier: AGPL-3.0-or-later + */ +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +/*#include <assert.h>*/ + +#if defined(USE_CONFIG_NETIO_POSIX) || defined(USE_CONFIG_COROUTINE) +# define CONFIG_NETIO_NUM_CONNS 8 +#endif +#ifdef USE_CONFIG_NETIO_POSIX +# define CONFIG_NETIO_ISLINUX 1 /* can we use Linux-kernel-specific fcntls? */ +# define CONFIG_NETIO_NUM_PORTS 1 +#endif + +#ifdef USE_CONFIG_COROUTINE +# define CONFIG_COROUTINE_DEFAULT_STACK_SIZE (8*1024) +# define CONFIG_COROUTINE_MEASURE_STACK 1 /* bool */ +# define CONFIG_COROUTINE_PROTECT_STACK 1 /* bool */ +# define CONFIG_COROUTINE_DEBUG 0 /* bool */ +# define CONFIG_COROUTINE_NUM (1 /* usb_common */ +\ + 1 /* usb_keyboard */ +\ + CONFIG_NETIO_NUM_CONNS /* accept+read */ +\ + (2*CONFIG_NETIO_NUM_CONNS) /* work+write */ ) + /*static_assert((CONFIG_COROUTINE_NUM * CONFIG_COROUTINE_DEFAULT_STACK_SIZE) < (264 * 1024)); */ +#endif + +#ifdef USE_CONFIG_TUSB +# include "tusb_config.h" +#endif + +#ifdef USE_CONFIG_9P +# define CONFIG_9P_PORT 564 + /** + * This max-msg-size is sized so that a Twrite message can return + * 8KiB of data. + * + * This is the same as the default in Plan 9 4e's lib9p; it has the + * comment that "24" is "ample room for Twrite/Rread header + * (iounit)". In fact, the Twrite header is only 23 bytes + * ("size[4] Twrite[1] tag[2] fid[4] offset[8] count[4]") and the + * Rread header is even shorter at 11 bytes ("size[4] Rread[1] + * tag[2] count[4]"), so "24" appears to be the size of the Twrite + * header rounded up to a nice round number. + * + * In older versions of 9P ("9P1"), the max message size was + * defined as part of the protocol specification rather than + * negotiated. In Plan 9 1e it was (8*1024)+128, and was bumped to + * (8*1024)+160 in 2e and 3e. + */ +# define CONFIG_9P_MAX_MSG_SIZE ((8*1024)+24) +# define CONFIG_9P_MAX_ERR_SIZE 128 /* 128 is what Plan 9 4e uses */ +#endif + +#endif /* _CONFIG_H */ diff --git a/coroutine.c b/coroutine.c index 2c265a3..f138fcf 100644 --- a/coroutine.c +++ b/coroutine.c @@ -14,10 +14,24 @@ /* Configuration **************************************************************/ -#define COROUTINE_NUM 5 -#define COROUTINE_MEASURE_STACK 1 -#define COROUTINE_PROTECT_STACK 1 -#define COROUTINE_DEBUG 0 +#define USE_CONFIG_COROUTINE +#include "config.h" + +#ifndef CONFIG_COROUTINE_DEFAULT_STACK_SIZE +# error config.h must define CONFIG_COROUTINE_DEFAULT_STACK_SIZE +#endif +#ifndef CONFIG_COROUTINE_NUM +# error config.h must define CONFIG_COROUTINE_NUM +#endif +#ifndef CONFIG_COROUTINE_MEASURE_STACK +# error config.h must define CONFIG_COROUTINE_MEASURE_STACK +#endif +#ifndef CONFIG_COROUTINE_PROTECT_STACK +# error config.h must define CONFIG_COROUTINE_PROTECT_STACK +#endif +#ifndef CONFIG_COROUTINE_DEBUG +# error config.h must define CONFIG_COROUTINE_DEBUG +#endif /* Implementation *************************************************************/ @@ -77,7 +91,7 @@ * sizes. (2) Leaving it uninitialized just gives me the willies. * * - Because embedded programs should be adverse to using the heap, - * COROUTINE_NUM is fixed, instead of having coroutine_add() + * CONFIG_COROUTINE_NUM is fixed, instead of having coroutine_add() * dynamically grow the coroutine_table as-needed. * * - On the flip-side, coroutine stacks are allocated on the heap @@ -105,7 +119,7 @@ * and a few bytes. */ -#if COROUTINE_DEBUG +#if CONFIG_COROUTINE_DEBUG # define debugf(...) printf("dbg: " __VA_ARGS__) #else # define debugf(...) @@ -146,8 +160,8 @@ struct coroutine { void *stack; }; -static struct coroutine coroutine_table[COROUTINE_NUM] = {0}; -static cid_t coroutine_running = 0; +static struct coroutine coroutine_table[CONFIG_COROUTINE_NUM] = {0}; +static cid_t coroutine_running = 0; static void call_with_stack(void *stack, cr_fn_t fn, void *args) { static void *saved_sp = NULL; @@ -193,13 +207,13 @@ static void call_with_stack(void *stack, cr_fn_t fn, void *args) { #endif } -#if COROUTINE_MEASURE_STACK || COROUTINE_PROTECT_STACK +#if CONFIG_COROUTINE_MEASURE_STACK || CONFIG_COROUTINE_PROTECT_STACK /* We just need a pattern that is unlikely to occur naturaly; this is * just a few bytes that I read from /dev/random. */ -static const uint8_t const stack_pattern[] = {0x1e, 0x15, 0x16, 0x0a, 0xcc, 0x52, 0x7e, 0xb7}; +static const uint8_t stack_pattern[] = {0x1e, 0x15, 0x16, 0x0a, 0xcc, 0x52, 0x7e, 0xb7}; #endif -#if COROUTINE_PROTECT_STACK +#if CONFIG_COROUTINE_PROTECT_STACK void assert_stack_protection(cid_t cid) { assert(coroutine_table[cid-1].stack_size); assert(coroutine_table[cid-1].stack); @@ -215,7 +229,7 @@ void assert_stack_protection(cid_t cid) { #define assert_cid_state(cid, opstate) do { \ assert((cid) > 0); \ - assert((cid) <= COROUTINE_NUM); \ + assert((cid) <= CONFIG_COROUTINE_NUM); \ assert(coroutine_table[(cid)-1].state opstate); \ assert_stack_protection(cid); \ } while (0) @@ -224,6 +238,9 @@ cid_t coroutine_add_with_stack_size(size_t stack_size, cr_fn_t fn, void *args) { static cid_t last_created = 0; cid_t parent = coroutine_running; + if (!stack_size) + stack_size = CONFIG_COROUTINE_DEFAULT_STACK_SIZE; + if (parent) assert_cid_state(parent, == CR_RUNNING); assert(stack_size); @@ -233,8 +250,8 @@ cid_t coroutine_add_with_stack_size(size_t stack_size, cr_fn_t fn, void *args) { cid_t child; { size_t idx_base = last_created; - for (size_t idx_shift = 0; idx_shift < COROUTINE_NUM; idx_shift++) { - child = ((idx_base + idx_shift) % COROUTINE_NUM) + 1; + for (size_t idx_shift = 0; idx_shift < CONFIG_COROUTINE_NUM; idx_shift++) { + child = ((idx_base + idx_shift) % CONFIG_COROUTINE_NUM) + 1; if (coroutine_table[child-1].state == CR_NONE) goto found; } @@ -247,7 +264,7 @@ cid_t coroutine_add_with_stack_size(size_t stack_size, cr_fn_t fn, void *args) { coroutine_table[child-1].stack_size = stack_size; coroutine_table[child-1].stack = malloc(stack_size); -#if COROUTINE_MEASURE_STACK || COROUTINE_PROTECT_STACK +#if CONFIG_COROUTINE_MEASURE_STACK || CONFIG_COROUTINE_PROTECT_STACK for (size_t i = 0; i < stack_size; i++) ((uint8_t*)coroutine_table[child-1].stack)[i] = stack_pattern[i%sizeof(stack_pattern)]; #endif @@ -256,7 +273,7 @@ cid_t coroutine_add_with_stack_size(size_t stack_size, cr_fn_t fn, void *args) { coroutine_table[child-1].state = CR_INITIALIZING; if (!setjmp(coroutine_add_env)) { /* point=a */ void *stack_base = coroutine_table[child-1].stack + (STACK_GROWS_DOWNWARD ? stack_size : 0); -#if COROUTINE_PROTECT_STACK +#if CONFIG_COROUTINE_PROTECT_STACK # if STACK_GROWS_DOWNWARD stack_base -= sizeof(stack_pattern); # else @@ -280,7 +297,7 @@ cid_t coroutine_add_with_stack_size(size_t stack_size, cr_fn_t fn, void *args) { void coroutine_main(void) { debugf("coroutine_main()\n"); bool ran; - for (coroutine_running = 1;; coroutine_running = (coroutine_running%COROUTINE_NUM)+1) { + for (coroutine_running = 1;; coroutine_running = (coroutine_running%CONFIG_COROUTINE_NUM)+1) { if (coroutine_running == 1) ran = false; struct coroutine *cr = &coroutine_table[coroutine_running-1]; @@ -294,13 +311,13 @@ void coroutine_main(void) { } assert_cid_state(coroutine_running, != CR_RUNNING); if (cr->state == CR_NONE) { -#if COROUTINE_MEASURE_STACK - size_t stack_size = cr->stack_size - (COROUTINE_PROTECT_STACK ? 2*sizeof(stack_pattern) : 0); +#if CONFIG_COROUTINE_MEASURE_STACK + size_t stack_size = cr->stack_size - (CONFIG_COROUTINE_PROTECT_STACK ? 2*sizeof(stack_pattern) : 0); size_t stack_used = stack_size; for (;;) { size_t i = STACK_GROWS_DOWNWARD - ? (COROUTINE_PROTECT_STACK ? sizeof(stack_pattern) : 0) + stack_size - stack_used - : stack_used - 1 - (COROUTINE_PROTECT_STACK ? sizeof(stack_pattern) : 0); + ? (CONFIG_COROUTINE_PROTECT_STACK ? sizeof(stack_pattern) : 0) + stack_size - stack_used + : stack_used - 1 - (CONFIG_COROUTINE_PROTECT_STACK ? sizeof(stack_pattern) : 0); if (stack_used == 0 || ((uint8_t*)cr->stack)[i] != stack_pattern[i%sizeof(stack_pattern)]) break; stack_used--; @@ -311,14 +328,14 @@ void coroutine_main(void) { coroutine_table[coroutine_running-1] = (struct coroutine){0}; } } - if (coroutine_running == COROUTINE_NUM && !ran) { + if (coroutine_running == CONFIG_COROUTINE_NUM && !ran) { fprintf(stderr, "error: no runnable coroutines\n"); return; } } } -bool cr_begin(void) { +void cr_begin(void) { assert_cid_state(coroutine_running, == CR_INITIALIZING); coroutine_table[coroutine_running-1].state = CR_RUNNABLE; diff --git a/coroutine.h b/coroutine.h index d02d0f9..4bdc4f8 100644 --- a/coroutine.h +++ b/coroutine.h @@ -29,10 +29,6 @@ #include <stddef.h> /* for size_t */ #include <stdbool.h> /* for bool */ -/* configuration **************************************************************/ - -#define COROUTINE_DEFAULT_STACK_SIZE (8*1024) - /* typedefs *******************************************************************/ /** @@ -94,7 +90,7 @@ cid_t coroutine_add_with_stack_size(size_t stack_size, cr_fn_t fn, void *args); * Like coroutine_add_with_stack_size(), but uses a default stack size so * you don't need to think about it. */ -#define coroutine_add(fn, args) coroutine_add_with_stack_size(COROUTINE_DEFAULT_STACK_SIZE, fn, args) +#define coroutine_add(fn, args) coroutine_add_with_stack_size(0, fn, args) /** * The main scheduler loop. @@ -109,7 +105,7 @@ void coroutine_main(void); /* inside of coroutines *******************************************************/ /** cr_begin() goes at the beginning of a coroutine, after it has initialized its stack. */ -bool cr_begin( void); +void cr_begin( void); /** cr_exit() terminates the currently-running coroutine. */ __attribute__ ((noreturn)) void cr_exit(void); /** cr_yield() switches to another coroutine (if there is another runnable coroutine to switch to). */ @@ -1,13 +1,18 @@ #ifndef _NETIO_H_ #define _NETIO_H_ -#include <stdint.h> -#include <stdbool.h> +#include <stdbool.h> /* for bool */ +#include <stdint.h> /* for size_t, ssize_t, uint16_t */ +/** Return socket-fd on success, -errno on error. */ int netio_listen(uint16_t port); +/** Return connection-fd on success, -errno on error. */ int netio_accept(int sock); -size_t netio_read(int conn, void *buf, size_t count); -size_t netio_write(int conn, void *buf, size_t count); +/** Return bytes-read on success, 0 on EOF, -errno on error; a short read is *not* an error. */ +ssize_t netio_read(int conn, void *buf, size_t count); +/** Return `count` on success, -errno on error; a short write *is* an error. */ +ssize_t netio_write(int conn, void *buf, size_t count); +/** Return 0 on success, -errno on error. */ int netio_close(int conn, bool rd, bool wr); #endif /* _NETIO_H_ */ diff --git a/netio_posix.c b/netio_posix.c index 3730f98..3cc00bb 100644 --- a/netio_posix.c +++ b/netio_posix.c @@ -1,7 +1,3 @@ -#define LINUX 1 -#define NUM_SOCKETS 1 -#define NUM_WORKERS 8 - #define _GNU_SOURCE #include <aio.h> /* for struct aiocb, aio_read(), aio_write(), aio_error(), aio_return(), SIGEV_SIGNAL */ #include <arpa/inet.h> /* for htons() */ @@ -12,7 +8,11 @@ #include <stdlib.h> /* for shutdown(), SHUT_RD, SHUT_WR, SHUT_RDWR */ #include <string.h> /* for memset() */ #include <sys/socket.h> /* for struct sockaddr, socket(), SOCK_* flags, setsockopt(), SOL_SOCKET, SO_REUSEADDR, bind(), listen(), accept() */ -#if LINUX + +#define USE_CONFIG_NETIO_POSIX +#include "config.h" + +#if CONFIG_NETIO_ISLINUX # include <fcntl.h> /* for fcntl(), F_SETFL, O_ASYNC, F_SETSIG */ #endif @@ -24,32 +24,32 @@ static int sigs_allocated = 0; static int sig_io = 0; -#if LINUX +#if CONFIG_NETIO_ISLINUX static int sig_accept = 0; #endif struct netio_socket { int fd; -#if LINUX - cid_t accept_waiters[NUM_WORKERS]; +#if CONFIG_NETIO_ISLINUX + cid_t accept_waiters[CONFIG_NETIO_NUM_CONNS]; #endif }; -static struct netio_socket socket_table[NUM_SOCKETS] = {0}; +static struct netio_socket socket_table[CONFIG_NETIO_NUM_PORTS] = {0}; static void handle_sig_io(int sig __attribute__ ((unused)), siginfo_t *info, void *ucontext __attribute__ ((unused))) { cr_unpause((cid_t)info->si_value.sival_int); } -#if LINUX +#if CONFIG_NETIO_ISLINUX static void handle_sig_accept(int sig __attribute__ ((unused)), siginfo_t *info, void *ucontext __attribute__ ((unused))) { struct netio_socket *sock = NULL; - for (int i = 0; sock == NULL && i < NUM_SOCKETS; i++) + for (int i = 0; sock == NULL && i < CONFIG_NETIO_NUM_PORTS; i++) if (info->si_fd == socket_table[i].fd) sock = &socket_table[i]; if (!sock) return; - for (int i = 0; i < NUM_WORKERS; i++) + for (int i = 0; i < CONFIG_NETIO_NUM_CONNS; i++) if (sock->accept_waiters[i] > 0) { cr_unpause(sock->accept_waiters[i]); sock->accept_waiters[i] = 0; @@ -73,7 +73,7 @@ static void _netio_init(void) { if (sigaction(sig_io, &action, NULL) < 0) error(1, errno, "sigaction"); -#if LINUX +#if CONFIG_NETIO_ISLINUX sig_accept = SIGRTMIN + (sigs_allocated++); if (sig_accept > SIGRTMAX) error(1, 0, "SIGRTMAX exceeded"); @@ -97,23 +97,23 @@ int netio_listen(uint16_t port) { /* Allocate a handle out of socket_table. */ handle = -1; - for (int i = 0; handle < 0 && i < NUM_SOCKETS; i++) + for (int i = 0; handle < 0 && i < CONFIG_NETIO_NUM_PORTS; i++) if (socket_table[i].fd == 0) handle = i; if (handle < 0) - error(1, 0, "NUM_SOCKETS exceeded"); + error(1, 0, "CONFIG_NETIO_NUM_PORTS exceeded"); sock = &socket_table[handle]; /* Bind the socket. */ memset(&addr, 0, sizeof addr); addr.in.sin_family = AF_INET; addr.in.sin_port = htons(port); - sock->fd = socket(AF_INET, SOCK_STREAM | (LINUX ? 0 : SOCK_NONBLOCK), 0); + sock->fd = socket(AF_INET, SOCK_STREAM | (CONFIG_NETIO_ISLINUX ? 0 : SOCK_NONBLOCK), 0); if (sock->fd < 0) error(1, errno, "socket"); if (setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)) < 0) error(1, errno, "setsockopt"); -#if LINUX +#if CONFIG_NETIO_ISLINUX if (fcntl(sock->fd, F_SETFL, O_ASYNC) < 0) error(1, errno, "fcntl(F_SETFL)"); if (fcntl(sock->fd, F_SETSIG, sig_accept) < 0) @@ -121,7 +121,7 @@ int netio_listen(uint16_t port) { #endif if (bind(sock->fd, &addr.gen, sizeof addr) < 0) error(1, errno, "bind"); - if (listen(sock->fd, NUM_WORKERS) < 0) + if (listen(sock->fd, CONFIG_NETIO_NUM_CONNS) < 0) error(1, errno, "listen"); /* Return. */ @@ -129,9 +129,9 @@ int netio_listen(uint16_t port) { } int netio_accept(int sock) { -#if LINUX +#if CONFIG_NETIO_ISLINUX int conn; - for (int i = 0; i < NUM_WORKERS; i++) + for (int i = 0; i < CONFIG_NETIO_NUM_CONNS; i++) if (socket_table[sock].accept_waiters[i] == 0) { socket_table[sock].accept_waiters[i] = cr_getcid(); break; @@ -156,7 +156,7 @@ int netio_accept(int sock) { #endif } -size_t netio_read(int conn, void *buf, size_t count) { +ssize_t netio_read(int conn, void *buf, size_t count) { int r; struct aiocb ctl_block = { .aio_fildes = conn, @@ -179,27 +179,33 @@ size_t netio_read(int conn, void *buf, size_t count) { return r ? -abs(r) : aio_return(&ctl_block); } -size_t netio_write(int conn, void *buf, size_t count) { - int r; - struct aiocb ctl_block = { - .aio_fildes = conn, - .aio_buf = buf, - .aio_nbytes = count, - .aio_sigevent = { - .sigev_notify = SIGEV_SIGNAL, - .sigev_signo = sig_io, - .sigev_value = { - .sival_int = (int)cr_getcid(), +ssize_t netio_write(int conn, void *buf, size_t goal) { + size_t done = 0; + while (done < goal) { + int r; + struct aiocb ctl_block = { + .aio_fildes = conn, + .aio_buf = &buf[done], + .aio_nbytes = goal-done, + .aio_sigevent = { + .sigev_notify = SIGEV_SIGNAL, + .sigev_signo = sig_io, + .sigev_value = { + .sival_int = (int)cr_getcid(), + }, }, - }, - }; + }; - if (aio_write(&ctl_block) < 0) - return -errno; + if (aio_write(&ctl_block) < 0) + return -errno; - while ((r = aio_error(&ctl_block)) == EINPROGRESS) - cr_pause_and_yield(); - return r ? -abs(r) : aio_return(&ctl_block); + while ((r = aio_error(&ctl_block)) == EINPROGRESS) + cr_pause_and_yield(); + if ((r) < 0) + return -abs(r); + done += aio_return(&ctl_block); + } + return done; } int netio_close(int conn, bool rd, bool wr) { |