summaryrefslogtreecommitdiff
path: root/lib9p
diff options
context:
space:
mode:
authorLuke T. Shumaker <lukeshu@lukeshu.com>2024-09-26 19:36:54 -0600
committerLuke T. Shumaker <lukeshu@lukeshu.com>2024-09-26 19:36:54 -0600
commit71e1a86a033c380f85dd300d788af63bfef25bab (patch)
tree07aa53d5a933ba51535a78972edbfe0cd95a31c5 /lib9p
parentf5da707e77ee954b12f3c961012e4f40fa4e1bd3 (diff)
wip reorg
Diffstat (limited to 'lib9p')
-rw-r--r--lib9p/.editorconfig3
-rw-r--r--lib9p/.gitignore2
-rw-r--r--lib9p/9P2000.L.txt56
-rw-r--r--lib9p/9P2000.e.txt16
-rw-r--r--lib9p/9P2000.txt82
-rw-r--r--lib9p/9P2000.u.txt20
-rw-r--r--lib9p/9p.h15
-rw-r--r--lib9p/defs.c89
-rwxr-xr-xlib9p/defs.gen456
-rw-r--r--lib9p/defs.h74
-rw-r--r--lib9p/internal.h139
-rwxr-xr-xlib9p/linux-errno.h.gen34
-rw-r--r--lib9p/misc.txt24
-rw-r--r--lib9p/srv.c194
-rw-r--r--lib9p/srv.h16
15 files changed, 1220 insertions, 0 deletions
diff --git a/lib9p/.editorconfig b/lib9p/.editorconfig
new file mode 100644
index 0000000..3f04564
--- /dev/null
+++ b/lib9p/.editorconfig
@@ -0,0 +1,3 @@
+[{defs.gen,linux-errno.h.gen}]
+indent_style = space
+indent_size = 4
diff --git a/lib9p/.gitignore b/lib9p/.gitignore
new file mode 100644
index 0000000..667e81c
--- /dev/null
+++ b/lib9p/.gitignore
@@ -0,0 +1,2 @@
+/defs-*
+/linux-errno.h
diff --git a/lib9p/9P2000.L.txt b/lib9p/9P2000.L.txt
new file mode 100644
index 0000000..71ab171
--- /dev/null
+++ b/lib9p/9P2000.L.txt
@@ -0,0 +1,56 @@
+# 9P2000.L.txt - Definitions of 9P2000.L messages
+#
+# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+# SPDX-Licence-Identifier: AGPL-3.0-or-later
+
+# "9P2000.L" Linux extension
+# https://github.com/chaos/diod/blob/master/protocol.md
+version "9P2000.L"
+
+from 9P2000.txt import *
+from 9P2000.u.txt import Tauth, Tattach
+
+#6/Tlerror = "illegal" # analogous to 106/Terror
+7/Rlerror = "ecode[4]" # analogous to 107/Rerror
+8/Tstatfs = "TODO"
+9/Rstatfs = "TODO"
+12/Tlopen = "TODO" # analogous to 112/Topen
+13/Rlopen = "TODO" # analogous to 113/Ropen
+14/Tlcreate = "TODO" # analogous to 114/Tcreate
+15/Rlcreate = "TODO" # analogous to 115/Rcreate
+16/Tsymlink = "TODO"
+17/Rsymlink = "TODO"
+18/Tmknod = "TODO"
+19/Rmknod = "TODO"
+20/Trename = "TODO"
+21/Rrename = "TODO"
+22/Treadlink = "TODO"
+23/Rreadlink = "TODO"
+24/Tgetattr = "TODO"
+25/Rgetattr = "TODO"
+26/Tsetattr = "TODO"
+27/Rsetattr = "TODO"
+#...
+30/Txattrwalk = "TODO"
+31/Rxattrwalk = "TODO"
+32/Txattrcreate = "TODO"
+33/Rxattrcreate = "TODO"
+#...
+40/Treaddir = "TODO"
+41/Rreaddir = "TODO"
+#...
+50/Tfsync = "TODO"
+51/Rfsync = "TODO"
+52/Tlock = "TODO"
+53/Rlock = "TODO"
+54/Tgetlock = "TODO"
+55/Rgetlock = "TODO"
+# ...
+70/Tlink = "TODO"
+71/Rlink = "TODO"
+72/Tmkdir = "TODO"
+73/Tmkdir = "TODO"
+74/Trenameat = "TODO"
+75/Rrenameat = "TODO"
+76/Tunlinkat = "TODO"
+77/Runlinkat = "TODO"
diff --git a/lib9p/9P2000.e.txt b/lib9p/9P2000.e.txt
new file mode 100644
index 0000000..22f056e
--- /dev/null
+++ b/lib9p/9P2000.e.txt
@@ -0,0 +1,16 @@
+# 9P2000e.e.txt - Definitions of 9P2000.e messages
+#
+# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+# SPDX-Licence-Identifier: AGPL-3.0-or-later
+
+# "9P2000.e" Erlang extension
+# https://erlangonxen.org/more/9p2000e
+# https://github.com/cloudozer/ling/blob/master/doc/9p2000e.md
+version "9P2000.e"
+
+150/Tsession = "key[8]"
+151/Rsession = ""
+152/Tsread = "fid[4] nwname[2] nwname*(wname[s])"
+153/Rsread = "data[d]"
+154/Tswrite = "Tswrite tag[2] fid[4] nwname[2] nwname*(wname[s]) data[d]"
+155/Rswrite = "count[4]"
diff --git a/lib9p/9P2000.txt b/lib9p/9P2000.txt
new file mode 100644
index 0000000..5f93cdf
--- /dev/null
+++ b/lib9p/9P2000.txt
@@ -0,0 +1,82 @@
+# 9P2000.txt - Definitions of 9P2000 messages
+#
+# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+# SPDX-Licence-Identifier: AGPL-3.0-or-later
+
+# The format of each message (excluding the "size[4] msg_type[1]
+# tag[2]" header) is written here as a sequence of
+# "member_name[member_type]" struct members.
+#
+# The primitive member types types are the following single-character
+# mnemonics:
+#
+# - 1 = u8
+# - 2 = u16le
+# - 4 = u32le
+# - 8 = u16le
+
+# "9P2000" base protocol
+# https://ericvh.github.io/9p-rfc/rfc9p2000.html
+# https://github.com/ericvh/9p-rfc/blob/master/9p2000.xml
+#
+# But due to incompleteness of the draft RFC, the Plan 9 manual
+# section-5 and the Plan 9 headers (particularly fcall.h) are a better
+# references.
+version "9P2000"
+
+# data (u32le `n`, then `n` bytes of data)
+d = "len[4] len*(dat[1])"
+
+# string (u16le `n`, then `n` bytes of UTF-8)
+s = "len[2] len*(utf8[1])"
+
+# qid (TODO)
+qid = "type[1] vers[4] path[8]"
+
+# stat (TODO)
+stat = "stat_size[2]"
+ "kern_type[2]"
+ "kern_dev[4]"
+ "file_qid[qid]"
+ "file_mode[4]"
+ "file_atime[4]"
+ "file_mtime[4]"
+ "file_size[8]"
+ "file_name[s]"
+ "file_owner_uid[s]"
+ "file_owner_gid[s]"
+ "file_last_modified_uid[s]"
+
+# In the 9P protocol, each message has a type, and message types come
+# in pairs (except "Rerror"); "T" and "R"; "T" messages are
+# client->server requests, and "R" messages are server->client
+# responses (I do not know what the Plan 9 designers intended "T" and
+# "R" to stand for). The type of a message is represented by a u8 ID.
+100/Tversion = "max_msg_size[4] version[s]"
+101/Rversion = "max_msg_size[4] version[s]"
+102/Tauth = "afid[4] uname[s] aname[s]"
+103/Rauth = "aqid[qid]"
+104/Tattach = "fid[4] afid[4] uname[s] aname[s]"
+105/Rattach = "qid[qid]"
+#106/Terror = "illegal"
+107/Rerror = "ename[s]"
+108/Tflush = "oldtag[2]"
+109/Rflush = ""
+110/Twalk = "fid[4] newfid[4] nwname[2] nwname*(wname[s])"
+111/Rwalk = "nwqid[2] nwqid*(wqid[qid])"
+112/Topen = "fid[4] mode[1]"
+113/Ropen = "qid[qid] iounit[4]"
+114/Tcreate = "fid[4] name[s] perm[4] mode[1]"
+115/Rcreate = "qid[qid] iounit[4]"
+116/Tread = "fid[4] offset[8] count[4]"
+117/Rread = "data[d]" # for directories data is the sequence "cnt*(entries[stat])"
+118/Twrite = "fid[4] offset[8] data[d]"
+119/Rwrite = "count[4]"
+120/Tclunk = "fid[4]"
+121/Rclunk = ""
+122/Tremove = "fid[4]"
+123/Rremove = ""
+124/Tstat = "fid[4]"
+125/Rstat = "stat[stat]"
+126/Twstat = "fid[4] stat[stat]"
+127/Rwstat = ""
diff --git a/lib9p/9P2000.u.txt b/lib9p/9P2000.u.txt
new file mode 100644
index 0000000..60d2b11
--- /dev/null
+++ b/lib9p/9P2000.u.txt
@@ -0,0 +1,20 @@
+# 9P2000.u.txt - Definitions of 9P2000.u messages
+#
+# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+# SPDX-Licence-Identifier: AGPL-3.0-or-later
+
+# "9P2000.u" Unix extension
+# https://ericvh.github.io/9p-rfc/rfc9p2000.u.html
+# https://github.com/ericvh/9p-rfc/blob/master/9p2000.u.xml
+version "9P2000.u"
+
+from 9P2000.txt import *
+
+stat += "file_extension[s]"
+ "file_owner_n_uid[4]"
+ "file_owner_n_gid[4]"
+ "file_last_modified_n_uid[4]"
+
+Tauth += "n_uname[4]"
+
+Rerror += "errno[4]"
diff --git a/lib9p/9p.h b/lib9p/9p.h
new file mode 100644
index 0000000..63fbd04
--- /dev/null
+++ b/lib9p/9p.h
@@ -0,0 +1,15 @@
+/* 9p/9p.h - TODO
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-Licence-Identifier: AGPL-3.0-or-later
+ */
+
+#include "9p/linux-errno.h"
+#include "9p/defs.h"
+#include "9p/defs-9P2000.h"
+/*#include "9p/defs-9P2000.u.h"*/
+
+#define P9_TYPECODE_FOR_CTYPE(msg) _Generic((in_msg) \
+ _P9_TYPECODE_FOR_CTYPE_9P2000(msg) \
+ /* _P9_TYPECODE_FOR_CTYPE_9P2000u(msg) */ \
+ )
diff --git a/lib9p/defs.c b/lib9p/defs.c
new file mode 100644
index 0000000..886a0c1
--- /dev/null
+++ b/lib9p/defs.c
@@ -0,0 +1,89 @@
+/* 9p/defs.c - TODO
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-Licence-Identifier: AGPL-3.0-or-later
+ */
+
+#include <inttypes.h> /* for PRIu{n} */
+#include <stdarg.h> /* for va_* */
+#include <stdio.h> /* for vsnprintf() */
+#include <string.h> /* for strncpy() */
+
+#include "9p/defs.h"
+#include "9p/linux-errno.h"
+#include "9p/internal.h"
+
+static struct version *versions[_P9_VER_CNT] = {
+ [P9_VER_9P2000] = &version_9P2000,
+ /* [P9_VER_9P2000u] = &version_9P2000u, */
+};
+
+int p9_error(struct p9_ctx *ctx, uint32_t linux_errno, char const *msg) {
+ strncpy(ctx->err_msg, msg, sizeof(ctx->err_msg));
+ ctx->err_msg[sizeof(ctx->err_msg)-1] = '\0';
+ ctx->err_num = linux_errno;
+ return -1;
+}
+
+int p9_errorf(struct p9_ctx *ctx, uint32_t linux_errno, char const *fmt, ...) {
+ int n;
+ va_list args;
+
+ va_start(args, fmt);
+ n = vsnprintf(ctx->err_msg, sizeof(ctx->err_msg), fmt, args);
+ va_end(args);
+ if ((size_t)(n+1) < sizeof(ctx->err_msg))
+ memset(&ctx->err_msg[n+1], 0, sizeof(ctx->err_msg)-(n+1));
+
+ ctx->err_num = linux_errno;
+
+ return -1;
+}
+
+size_t p9_unmarshal_size(struct p9_ctx *ctx, uint8_t *net_bytes) {
+ /* Header */
+ uint32_t net_len = decode_u32le(net_bytes);
+ if (net_len < 7)
+ return p9_error(ctx, LINUX_EBADMSG, "message is too short");
+ uint8_t typ = net_bytes[4];
+ uint32_t net_offset = 7;
+
+ /* Body */
+ if (!versions[ctx->version]->msgs[typ].unmarshal_extrasize)
+ return p9_errorf(ctx, LINUX_EOPNOTSUPP, "unknown message type %"PRIu8, typ);
+ size_t host_size = versions[ctx->version]->msgs[typ].unmarshal_basesize;
+ if (versions[ctx->version]->msgs[typ].unmarshal_extrasize(net_len, net_bytes, &net_offset, &host_size))
+ return p9_error(ctx, LINUX_EBADMSG, "message is too short for content");
+
+ return host_size;
+}
+
+uint8_t p9_unmarshal(struct p9_ctx *ctx, uint8_t *net_bytes, uint16_t *out_tag, void *out_body) {
+ /* Header */
+ uint8_t typ = net_bytes[4];
+ *out_tag = decode_u16le(&net_bytes[5]);
+ uint32_t net_offset = 7;
+
+ /* Body */
+ void *host_extra = out_body + versions[ctx->version]->msgs[typ].unmarshal_basesize;
+ if (versions[ctx->version]->msgs[typ].unmarshal(net_bytes, &net_offset, &host_extra, out_body))
+ return p9_error(ctx, LINUX_EBADMSG, "message contains invalid UTF-8");
+
+ return typ;
+}
+
+uint32_t _p9_marshal(struct p9_ctx *ctx, uint8_t typ, uint16_t msgid, void *body, uint8_t *out_bytes) {
+ /* Header */
+ out_bytes[4] = typ;
+ encode_u16le(msgid, &out_bytes[5]);
+ uint32_t net_offset = 7;
+
+ /* Body */
+ if (versions[ctx->version]->msgs[typ].marshal(ctx, body, out_bytes, &net_offset))
+ return 0;
+
+ /* Header, again */
+ encode_u32le(net_offset, out_bytes);
+
+ return net_offset;
+}
diff --git a/lib9p/defs.gen b/lib9p/defs.gen
new file mode 100755
index 0000000..1a8dc69
--- /dev/null
+++ b/lib9p/defs.gen
@@ -0,0 +1,456 @@
+#!/usr/bin/env python
+# 9p/defs.gen - Generate C marshalers/unmarshalers for a .txt file
+# defining a 9P protocol variant.
+#
+# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+# SPDX-Licence-Identifier: AGPL-3.0-or-later
+
+import enum
+import os.path
+import re
+
+PROGRAM = "./9p/defs.gen"
+
+# Parse the *.txt ##############################################################
+
+
+class Atom(enum.Enum):
+ u8 = 1
+ u16 = 2
+ u32 = 4
+ u64 = 8
+
+ @property
+ def name(self) -> str:
+ return str(self.value)
+
+ @property
+ def static_size(self) -> int:
+ return self.value
+
+
+# `msgid/structname = "member1 member2..."`
+# `structname = "member1 member2..."`
+# `structname += "member1 member2..."`
+class Struct:
+ msgid: int | None = None
+ name: str
+ members: list["Member"]
+
+ @property
+ def static_size(self) -> int | None:
+ size = 0
+ for member in self.members:
+ msize = member.static_size
+ if msize is None:
+ return None
+ size += msize
+ return size
+
+
+# `cnt*(name[typ])`
+# the `cnt*(...)` wrapper is optional
+class Member:
+ cnt: str | None = None
+ name: str
+ typ: Atom | Struct
+
+ @property
+ def static_size(self) -> int | None:
+ if self.cnt:
+ return None
+ return self.typ.static_size
+
+
+re_membername = "(?:[a-zA-Z_][a-zA-Z_0-9]*)"
+re_memberspec = (
+ f"(?:(?P<cnt>{re_membername})\\*\\()?(?P<name>{re_membername})\\[(?P<typ>.*)\\]\\)?"
+)
+
+
+def parse_members(
+ env: dict[str, Atom | Struct], existing: list[Member], specs: str
+) -> list[Member]:
+ ret = existing
+ for spec in specs.split():
+ m = re.fullmatch(re_memberspec, spec)
+ if not m:
+ raise SyntaxError(f"invalid member spec {repr(spec)}")
+
+ member = Member()
+
+ member.name = m.group("name")
+ if any(x.name == member.name for x in ret):
+ raise ValueError(f"duplicate member name {repr(member.name)}")
+
+ if m.group("typ") not in env:
+ raise NameError(f"Unknown type {repr(m.group(2))}")
+ member.typ = env[m.group("typ")]
+
+ if cnt := m.group("cnt"):
+ if len(ret) == 0 or ret[-1].name != cnt:
+ raise ValueError(f"list count must be previous item: {repr(cnt)}")
+ if not isinstance(ret[-1].typ, Atom):
+ raise ValueError(f"list count must be an integer type: {repr(cnt)}")
+ member.cnt = cnt
+
+ ret += [member]
+ return ret
+
+
+re_version = r'version\s+"(?P<version>[^"]+)"'
+re_structspec = (
+ r'(?:(?P<msgid>[0-9]+)/)?(?P<name>\S+)\s*(?P<op>\+?=)\s*"(?P<members>[^"]*)"'
+)
+re_structspec_cont = r'"(?P<members>[^"]*)"'
+
+
+def parse_file(filename: str) -> tuple[str, list[Struct]]:
+ version: str | None = None
+ env: dict[str, Atom | Struct] = {
+ "1": Atom.u8,
+ "2": Atom.u16,
+ "4": Atom.u32,
+ "8": Atom.u64,
+ }
+ with open(filename, "r") as fh:
+ prev: Struct | None = None
+ for line in fh:
+ line = line.split("#", 1)[0].strip()
+ if not line:
+ continue
+ if m := re.fullmatch(re_version, line):
+ if version:
+ raise SyntaxError("must have exactly 1 version line")
+ version = m.group("version")
+ elif m := re.fullmatch(re_structspec, line):
+ if m.group("op") == "+=" and m.group("msgid"):
+ raise SyntaxError("cannot += to a message that is not yet defined")
+ match m.group("op"):
+ case "=":
+ struct = Struct()
+ if m.group("msgid"):
+ struct.msgid = int(m.group("msgid"))
+ struct.name = m.group("name")
+ struct.members = parse_members(env, [], m.group("members"))
+ env[struct.name] = struct
+ prev = struct
+ case "+=":
+ if m.group("name") not in env:
+ raise NameError(f"Unknown type {repr(m.group('name'))}")
+ _struct = env[m.group("name")]
+ if not isinstance(_struct, Struct):
+ raise NameError(
+ f"Type {repr(m.group('name'))} is not a struct"
+ )
+ struct = _struct
+ struct.members = parse_members(
+ env, struct.members, m.group("members")
+ )
+ prev = struct
+ elif m := re.fullmatch(re_structspec_cont, line):
+ if not prev:
+ raise SyntaxError("continuation line must come after a struct line")
+ prev.members = parse_members(env, prev.members, m.group("members"))
+ else:
+ raise SyntaxError(f"invalid line {repr(line)}")
+ if not version:
+ raise SyntaxError("must have exactly 1 version line")
+ structs = [x for x in env.values() if isinstance(x, Struct)]
+ return version, structs
+
+
+# Generate C ###################################################################
+
+
+def c_typename(idprefix: str, typ: Atom | Struct) -> str:
+ match typ:
+ case Atom():
+ return f"uint{typ.value*8}_t"
+ case Struct():
+ if typ.msgid is not None:
+ return f"struct {idprefix}msg_{typ.name}"
+ return f"struct {idprefix}{typ.name}"
+ case _:
+ raise ValueError(f"not a type: {typ.__class__.__name__}")
+
+
+def gen_h(txtname: str, idprefix: str, structs: list[Struct]) -> str:
+ guard = (
+ f"_{txtname.replace('.txt', '.h').upper().replace('/', '_').replace('.', '_')}_"
+ )
+ ret = f"""/* Generated by `{PROGRAM} {txtname}`. DO NOT EDIT! */
+
+#ifndef {guard}
+#define {guard}
+
+#define {idprefix.upper()}MIN_MSGLEN 7
+"""
+ ret += """
+/* non-message structs ********************************************************/
+
+"""
+ for struct in structs:
+ if struct.msgid is not None:
+ continue
+ ret += c_typename(idprefix, struct) + " {\n"
+ typewidth = max(
+ len(c_typename(idprefix, member.typ)) for member in struct.members
+ )
+ for member in struct.members:
+ ret += f"\t{c_typename(idprefix, member.typ).ljust(typewidth)} {'*' if member.cnt else ' '}{member.name};\n"
+ ret += "};\n"
+
+ ret += """
+/* message types **************************************************************/
+
+"""
+ ret += f"enum {idprefix}msg_type {{ /* uint8_t */\n"
+ namewidth = max(len(msg.name) for msg in structs if msg.msgid is not None)
+ for msg in structs:
+ if msg.msgid is None:
+ continue
+ ret += f"\t{idprefix.upper()}TYP_{msg.name.ljust(namewidth)} = {msg.msgid},\n"
+ ret += "};\n"
+
+ ret += """
+/* message structs ************************************************************/
+
+"""
+ for msg in structs:
+ if msg.msgid is None:
+ continue
+ if not msg.members:
+ ret += c_typename(idprefix, msg) + " {};\n"
+ continue
+ ret += c_typename(idprefix, msg) + " {\n"
+ typewidth = max(len(c_typename(idprefix, member.typ)) for member in msg.members)
+ for member in msg.members:
+ ret += f"\t{c_typename(idprefix, member.typ).ljust(typewidth)} {'*' if member.cnt else ' '}{member.name};\n"
+ ret += "};\n"
+
+ ret += f"""
+/* tables *********************************************************************/
+
+#define _P9_TYPECODE_FOR_CTYPE(msg) """
+ for msg in structs:
+ if msg.msgid is None:
+ continue
+ ret += f", \\\n\t\t{c_typename(idprefix, msg)}: {idprefix.upper()}TYP_{msg.name}"
+ ret += "\n"
+
+ ret += "\n"
+ ret += f"#endif /* {guard} */\n"
+ return ret
+
+
+def gen_c(txtname: str, idprefix: str, structs: list[Struct]) -> str:
+ txtdir, txtbase = os.path.split(txtname)
+ header = os.path.join(txtdir, "defs-" + txtbase.replace(".txt", ".h"))
+ ret = f"""/* Generated by `{PROGRAM} {txtname}`. DO NOT EDIT! */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h> /* for malloc() */
+
+#include "{header}"
+#include "9p/internal.h"
+"""
+
+ def used(arg: str) -> str:
+ return arg
+
+ def unused(arg: str) -> str:
+ return f"UNUSED({arg})"
+
+ # checksize_* ##############################################################
+ ret += """
+/* checksize_* ****************************************************************/
+
+static inline bool _checksize_list(size_t cnt, _checksize_fn_t fn, size_t host_size,
+ uint32_t net_len, uint8_t *net_bytes, uint32_t *mut_net_offset, size_t *mut_host_extra) {
+ for (size_t i = 0; i < cnt; i++)
+ if (__builtin_add_overflow(*mut_host_extra, host_size, mut_host_extra)
+ || fn(net_len, net_bytes, mut_net_offset, mut_host_extra))
+ return true;
+ return false;
+}
+
+static inline bool checksize_1(uint32_t net_len, uint8_t *UNUSED(net_bytes), uint32_t *mut_net_offset, size_t *UNUSED(mut_host_extra)) {
+ return __builtin_add_overflow(*mut_net_offset, 1, mut_net_offset) || net_len < *mut_net_offset;
+}
+static inline bool checksize_2(uint32_t net_len, uint8_t *UNUSED(net_bytes), uint32_t *mut_net_offset, size_t *UNUSED(mut_host_extra)) {
+ return __builtin_add_overflow(*mut_net_offset, 2, mut_net_offset) || net_len < *mut_net_offset;
+}
+static inline bool checksize_4(uint32_t net_len, uint8_t *UNUSED(net_bytes), uint32_t *mut_net_offset, size_t *UNUSED(mut_host_extra)) {
+ return __builtin_add_overflow(*mut_net_offset, 4, mut_net_offset) || net_len < *mut_net_offset;
+}
+static inline bool checksize_8(uint32_t net_len, uint8_t *UNUSED(net_bytes), uint32_t *mut_net_offset, size_t *UNUSED(mut_host_extra)) {
+ return __builtin_add_overflow(*mut_net_offset, 8, mut_net_offset) || net_len < *mut_net_offset;
+}
+
+"""
+ for struct in structs:
+ inline = ' inline' if struct.msgid is None else ''
+ argfn = used if struct.members else unused
+ ret += f"static{inline} bool checksize_{struct.name}(uint32_t {argfn('net_len')}, uint8_t *{argfn('net_bytes')}, uint32_t *{argfn('mut_net_offset')}, size_t *{argfn('mut_host_extra')}) {{"
+ if len(struct.members) == 0:
+ ret += "\n\treturn false;\n"
+ ret += "}\n"
+ continue
+ prefix0 = "\treturn "
+ prefix1 = "\t || "
+ prefix2 = "\t "
+ prefix = prefix0
+ prev_size: int | None = None
+ for member in struct.members:
+ if member.cnt is not None:
+ assert prev_size
+ ret += f"\n{prefix }_checksize_list(decode_u{prev_size*8}le(&net_bytes[(*mut_net_offset)-{prev_size}]), checksize_{member.typ.name}, sizeof({c_typename(idprefix, member.typ)}),"
+ ret += f"\n{prefix2} net_len, net_bytes, mut_net_offset, mut_host_extra)"
+ else:
+ ret += f"\n{prefix}checksize_{member.typ.name}(net_len, net_bytes, mut_net_offset, mut_host_extra)"
+ prefix = prefix1
+ prev_size = member.static_size
+ if struct.name == "s":
+ ret += (
+ f"\n{prefix}__builtin_add_overflow(*mut_host_extra, 1, mut_host_extra)"
+ )
+ ret += ";\n}\n"
+
+ # unmarshal_* ##############################################################
+ ret += """
+/* unmarshal_* ****************************************************************/
+/* checksize_XXX() should be called before unmarshal_XXX(). */
+
+static inline bool unmarshal_1(uint8_t *net_bytes, uint32_t *mut_net_offset, void **UNUSED(mut_host_extra), uint8_t *out) {
+ *out = decode_u8le(&net_bytes[*mut_net_offset]);
+ *mut_net_offset += 1;
+ return false;
+}
+static inline bool unmarshal_2(uint8_t *net_bytes, uint32_t *mut_net_offset, void **UNUSED(mut_host_extra), uint16_t *out) {
+ *out = decode_u16le(&net_bytes[*mut_net_offset]);
+ *mut_net_offset += 2;
+ return false;
+}
+static inline bool unmarshal_4(uint8_t *net_bytes, uint32_t *mut_net_offset, void **UNUSED(mut_host_extra), uint32_t *out) {
+ *out = decode_u32le(&net_bytes[*mut_net_offset]);
+ *mut_net_offset += 4;
+ return false;
+}
+static inline bool unmarshal_8(uint8_t *net_bytes, uint32_t *mut_net_offset, void **UNUSED(mut_host_extra), uint64_t *out) {
+ *out = decode_u64le(&net_bytes[*mut_net_offset]);
+ *mut_net_offset += 8;
+ return false;
+}
+
+"""
+ for struct in structs:
+ argfn = used if struct.members else unused
+ ret += f"static inline bool unmarshal_{struct.name}(uint8_t *{argfn('net_bytes')}, uint32_t *{argfn('mut_net_offset')}, void **{argfn('mut_host_extra')}, {c_typename(idprefix, struct)} *{argfn('out')}) {{\n"
+ for member in struct.members:
+ if member.cnt:
+ ret += f"\tout->{member.name} = *mut_host_extra;\n"
+ ret += f"\t*mut_host_extra += sizeof(*out->{member.name}) * out->{member.cnt};\n"
+ ret += f"\tfor (typeof(out->{member.cnt}) i = 0; i < out->{member.cnt}; i++)\n"
+ ret += f"\t\tif (unmarshal_{member.typ.name}(net_bytes, mut_net_offset, mut_host_extra, &(out->{member.name}[i])))\n"
+ ret += f"\t\t\treturn true;\n"
+ if struct.name == "s":
+ ret += f"\tif (!is_valid_utf8_without_nul(out->{member.name}, out->{member.cnt}))\n"
+ ret += f"\t\treturn true;\n"
+ ret += f"\tout->{member.name}[out->{member.cnt}] = '\\0';\n"
+ else:
+ ret += f"\tif (unmarshal_{member.typ.name}(net_bytes, mut_net_offset, mut_host_extra, &(out->{member.name})))\n"
+ ret += f"\t\treturn true;\n"
+ ret += "\treturn false;\n"
+ ret += "}\n"
+
+ # marshal_* ################################################################
+ ret += """
+/* marshal_* ******************************************************************/
+
+static inline bool marshal_1(struct p9_ctx *ctx, uint8_t val, uint8_t *out_net_bytes, uint32_t *mut_net_offset) {
+ if (*mut_net_offset + 1 > ctx->max_msg_size)
+ return true;
+ out_net_bytes[*mut_net_offset] = val;
+ *mut_net_offset += 1;
+ return false;
+}
+static inline bool marshal_2(struct p9_ctx *ctx, uint16_t val, uint8_t *out_net_bytes, uint32_t *mut_net_offset) {
+ if (*mut_net_offset + 2 > ctx->max_msg_size)
+ return true;
+ encode_u16le(val, &out_net_bytes[*mut_net_offset]);
+ *mut_net_offset += 2;
+ return false;
+}
+static inline bool marshal_4(struct p9_ctx *ctx, uint32_t val, uint8_t *out_net_bytes, uint32_t *mut_net_offset) {
+ if (*mut_net_offset + 4 > ctx->max_msg_size)
+ return true;
+ encode_u32le(val, &out_net_bytes[*mut_net_offset]);
+ *mut_net_offset += 4;
+ return false;
+}
+static inline bool marshal_8(struct p9_ctx *ctx, uint64_t val, uint8_t *out_net_bytes, uint32_t *mut_net_offset) {
+ if (*mut_net_offset + 8 > ctx->max_msg_size)
+ return true;
+ encode_u64le(val, &out_net_bytes[*mut_net_offset]);
+ *mut_net_offset += 8;
+ return false;
+}
+"""
+ for struct in structs:
+ argfn = used if struct.members else unused
+ ret += f"static inline bool marshal_{struct.name}(struct p9_ctx *{argfn('ctx')}, {c_typename(idprefix, struct)} {argfn('val')}, uint8_t *{argfn('out_net_bytes')}, uint32_t *{argfn('mut_net_offset')}) {{\n"
+ for member in struct.members:
+ if member.cnt:
+ ret += f"\tfor (typeof(val.{member.cnt}) i = 0; i < val.{member.cnt}; i++)\n"
+ ret += f"\t\tif (marshal_{member.typ.name}(ctx, val.{member.name}[i], out_net_bytes, mut_net_offset))\n"
+ ret += f"\t\t\treturn true;\n"
+ else:
+ ret += f"\tif (marshal_{member.typ.name}(ctx, val.{member.name}, out_net_bytes, mut_net_offset))\n"
+ ret += f"\t\treturn true;\n"
+ ret += "\treturn false;\n"
+ ret += "}\n"
+
+ # tables ###################################################################
+ ret += """
+/* tables *********************************************************************/
+
+"""
+ for msg in structs:
+ if msg.msgid is None:
+ continue
+ ret += f"static bool _unmarshal_{msg.name}(uint8_t *net_bytes, uint32_t *mut_net_offset, void **mut_host_extra, void *out) {{ return unmarshal_{msg.name}(net_bytes, mut_net_offset, mut_host_extra, ({c_typename(idprefix, msg)} *)out); }}\n"
+ for msg in structs:
+ if msg.msgid is None:
+ continue
+ ret += f"static bool _marshal_{msg.name}(struct p9_ctx *ctx, void *val, uint8_t *out_net_bytes, uint32_t *mut_net_offset) {{ return marshal_{msg.name}(ctx, *(({c_typename(idprefix, msg)} *)val), out_net_bytes, mut_net_offset); }}\n"
+ ret += "struct version version_9P2000 = {\n"
+ ret += "\t.msgs = {\n"
+ for msg in structs:
+ if msg.msgid is None:
+ continue
+ ret += f"\t\t[{idprefix.upper()}TYP_{msg.name}] = {{ .unmarshal_basesize=sizeof({c_typename(idprefix, msg)}), .unmarshal_extrasize=checksize_{msg.name}, .unmarshal=_unmarshal_{msg.name}, .marshal=_marshal_{msg.name} }},\n"
+ ret += "\t},\n"
+ ret += "};\n"
+
+ ############################################################################
+ return ret
+
+
+################################################################################
+
+if __name__ == "__main__":
+ import sys
+
+ for txtname in sys.argv[1:]:
+ txtdir, txtbase = os.path.split(txtname)
+ version, structs = parse_file(txtname)
+ with open(
+ os.path.join(txtdir, "defs-" + txtbase.replace(".txt", ".h")), "w"
+ ) as fh:
+ fh.write(gen_h(txtname, "p9_", structs))
+ with open(
+ os.path.join(txtdir, "defs-" + txtbase.replace(".txt", ".c")), "w"
+ ) as fh:
+ fh.write(gen_c(txtname, "p9_", structs))
diff --git a/lib9p/defs.h b/lib9p/defs.h
new file mode 100644
index 0000000..907cdde
--- /dev/null
+++ b/lib9p/defs.h
@@ -0,0 +1,74 @@
+/* 9p/defs.h - TODO
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-Licence-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _9P_DEFS_H_
+#define _9P_DEFS_H_
+
+#include <stdint.h>
+
+#define P9_NOTAG ((uint16_t)~0U)
+#define P9_NOFID ((uint32_t)~0U)
+
+enum p9_version {
+ P9_VER_UNINITIALIZED,
+ /* P9_VER_9P1, */
+ P9_VER_9P2000,
+ /*P9_VER_9P2000_u,*/
+ /*P9_VER_9P2000_L,*/
+ /*P9_VER_9P2000_e,*/
+ _P9_VER_CNT,
+};
+
+struct p9_ctx;
+
+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);
+/** Write a printf-style error into ctx, return -1. */
+int p9_errorf(struct p9_ctx *ctx, uint32_t linux_errno, char const *fmt, ...);
+
+/**
+ * Return how much space the message at net_bytes will take when
+ * unmarshaled. This number may be larger than net_bytes due to (1)
+ * struct padding, (2) nul-terminator byes for strings.
+ *
+ * Emits an error (return -1, set ctx->err_num and ctx->err_msg) if
+ * either the message type is unknown or if net_bytes is too short for
+ * that message type.
+ *
+ * @param net_bytes : the complete request, starting with the "size[4]"
+ * @return required size, or -1 on error
+ */
+size_t p9_unmarshal_size(struct p9_ctx *ctx, uint8_t *net_bytes);
+
+/**
+ * Unmarshal the 9P message `net_bytes` into the C struct `out_body`.
+ *
+ * Emits an error (return 0, set ctx->err_num and ctx->err_msg) if a
+ * string contains invalid UTF-8 or a nul-byte.
+ *
+ * @param net_bytes : the complete message, starting with the "size[4]"
+ * @param out_tag : the message-ID tag
+ * @param out_body : the message body, must be at least p9_unmarshal_size() bytes
+ * @return the message type, or -1 on error
+ */
+uint8_t p9_unmarshal(struct p9_ctx *ctx, uint8_t *net_bytes, uint16_t *out_tag, void *out_body);
+
+/**
+ * Marshal a `struct p9_msg_{type}` structure into a byte-array.
+ *
+ * @param struct p9_ctx *ctx : a request context
+ * @param uint16_t msgid : the message-ID tag
+ * @param struct p9_msg_{type} msg : the message to encode
+ *
+ * @param uint8_t *out_bytes : the buffer to encode to, must be at be at least ctx->max_msg_size bytes
+ * @return uint32_t : the encoded length, or -1 on error
+ */
+#define p9_marshal(ctx, msgid, msg, out_bytes) _p9_marshal(ctx, P9_TYPECODE_FOR_CTYPE(msg), msgid, &(msg), out_bytes)
+uint32_t _p9_marshal(struct p9_ctx *ctx, uint8_t typ, uint16_t msgid, void *body, uint8_t *out_bytes);
+
+#endif /* _9P_DEFS_H_ */
diff --git a/lib9p/internal.h b/lib9p/internal.h
new file mode 100644
index 0000000..61977d4
--- /dev/null
+++ b/lib9p/internal.h
@@ -0,0 +1,139 @@
+/* 9p/internal.h - TODO
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-Licence-Identifier: AGPL-3.0-or-later
+ */
+
+#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);
+typedef bool (*_unmarshal_fn_t)(uint8_t *net_bytes, uint32_t *mut_net_offset, void **mut_host_extra, void *out);
+typedef bool (*_marshal_fn_t)(struct p9_ctx *ctx, void *val, uint8_t *out_net_bytes, uint32_t *mut_net_offset);
+
+struct msg_vtable {
+ size_t unmarshal_basesize;
+ _checksize_fn_t unmarshal_extrasize;
+ _unmarshal_fn_t unmarshal;
+ _marshal_fn_t marshal;
+};
+
+struct version {
+ struct msg_vtable msgs[0xFF];
+};
+
+extern struct version version_9P2000;
+/*extern struct version version_9P2000u; */
+
+/* unmarshal utilities ********************************************************/
+
+static inline uint8_t decode_u8le(uint8_t *in) {
+ return in[0];
+}
+static inline uint16_t decode_u16le(uint8_t *in) {
+ return (((uint16_t)(in[0])) << 0)
+ | (((uint16_t)(in[1])) << 8)
+ ;
+}
+static inline uint32_t decode_u32le(uint8_t *in) {
+ return (((uint32_t)(in[0])) << 0)
+ | (((uint32_t)(in[1])) << 8)
+ | (((uint32_t)(in[2])) << 16)
+ | (((uint32_t)(in[3])) << 24)
+ ;
+}
+static inline uint64_t decode_u64le(uint8_t *in) {
+ return (((uint64_t)(in[0])) << 0)
+ | (((uint64_t)(in[1])) << 8)
+ | (((uint64_t)(in[2])) << 16)
+ | (((uint64_t)(in[3])) << 24)
+ | (((uint64_t)(in[4])) << 32)
+ | (((uint64_t)(in[5])) << 40)
+ | (((uint64_t)(in[6])) << 48)
+ | (((uint64_t)(in[7])) << 56)
+ ;
+}
+
+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) { 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 ((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) {
+ out[0] = in;
+}
+static inline void encode_u16le(uint16_t in, uint8_t *out) {
+ out[0] = (uint8_t)((in >> 0) & 0xFF);
+ out[1] = (uint8_t)((in >> 8) & 0xFF);
+}
+static inline void encode_u32le(uint32_t in, uint8_t *out) {
+ out[0] = (uint8_t)((in >> 0) & 0xFF);
+ out[1] = (uint8_t)((in >> 8) & 0xFF);
+ out[2] = (uint8_t)((in >> 16) & 0xFF);
+ out[3] = (uint8_t)((in >> 24) & 0xFF);
+}
+static inline void encode_u64le(uint64_t in, uint8_t *out) {
+ out[0] = (uint8_t)((in >> 0) & 0xFF);
+ out[1] = (uint8_t)((in >> 8) & 0xFF);
+ out[2] = (uint8_t)((in >> 16) & 0xFF);
+ out[3] = (uint8_t)((in >> 24) & 0xFF);
+ out[4] = (uint8_t)((in >> 32) & 0xFF);
+ out[5] = (uint8_t)((in >> 40) & 0xFF);
+ out[6] = (uint8_t)((in >> 48) & 0xFF);
+ out[7] = (uint8_t)((in >> 56) & 0xFF);
+}
+
+#endif /* _9P_INTERNAL_H_ */
diff --git a/lib9p/linux-errno.h.gen b/lib9p/linux-errno.h.gen
new file mode 100755
index 0000000..b896384
--- /dev/null
+++ b/lib9p/linux-errno.h.gen
@@ -0,0 +1,34 @@
+#!/usr/bin/env python
+
+
+def print_errnos(txtlists: list[str]) -> None:
+ print(
+ f"/* 9p/linux-errno.h - Generated by `./9p/linux-errno.h.gen {' '.join(txtlists)}`. DO NOT EDIT! */"
+ )
+ errnos: dict[str, tuple[int, str]] = {}
+ for txtlist in sys.argv[1:]:
+ with open(txtlist, "r") as fh:
+ for line in fh:
+ if line.startswith("#"):
+ print(f"/* {line[1:].strip()} */")
+ continue
+ _num, name, desc = line.split(maxsplit=2)
+ num = int(_num)
+ desc = desc.strip()
+ errnos[name] = (num, desc)
+ print()
+ print("#ifndef _9P_LINUX_ERRNO_H_")
+ print("#define _9P_LINUX_ERRNO_H_")
+ print()
+ namelen = max(len(name) for name in errnos.keys())
+ numlen = max(len(str(num)) for (num, desc) in errnos.values())
+ for name in errnos:
+ print(f"#define LINUX_{name.ljust(namelen)} {str(errnos[name][0]).rjust(numlen)} /* {errnos[name][1]} */")
+ print()
+ print("#endif /* _9P_LINUX_ERRNO_H_ */")
+
+
+if __name__ == "__main__":
+ import sys
+
+ print_errnos(sys.argv[1:])
diff --git a/lib9p/misc.txt b/lib9p/misc.txt
new file mode 100644
index 0000000..93aa304
--- /dev/null
+++ b/lib9p/misc.txt
@@ -0,0 +1,24 @@
+# qid.types
+QTDIR = 1<<7
+QTAPPEND = 1<<6
+QTEXCL = 1<<5
+QTMOUNT = 1<<4 # been around forever, but undocumented?
+QTAUTH = 1<<3
+QTTMP = 1<<2 # added to Plan 9 2003-12
+QTSYMLINK = 1<<1 # .u
+QTFILE = 1<<0
+
+DMDIR = 1<<31
+DMAPPEND = 1<<30
+DMEXCL = 1<<29
+DMMOUNT = 1<<28
+DMAUTH = 1<<27
+DMTMP = 1<<26
+# = 1<<25
+# = 1<<24
+DMDEVICE = 1<<23 # .u
+# = 1<<22
+DMNAMEDPIPE = 1<<21 # .u
+DMSOCKET = 1<<20 # .u
+DMSETUID = 1<<19 # .u
+DMSETGID = 1<<18 # .u
diff --git a/lib9p/srv.c b/lib9p/srv.c
new file mode 100644
index 0000000..59326c9
--- /dev/null
+++ b/lib9p/srv.c
@@ -0,0 +1,194 @@
+#include <assert.h>
+
+#include "coroutine.h"
+#include "netio.h"
+#include "9p/9p.h"
+
+#include "9p/internal.h"
+
+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;
+};
+
+struct p9_srvreq {
+ p9_srvconn *conn;
+ uint8_t *msg;
+};
+
+COROUTINE p9_srv_read_cr(void *_srv) {
+ uint8_t buf[CONFIG_9P_MAX_MSG_SIZE];
+
+ p9_srv *srv = _srv;
+ assert(srv);
+ cr_begin();
+
+ for (;;) {
+ 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);
+}
+
+ERANGE for reply too large
+EPROTONOSUPPORT for version errors
diff --git a/lib9p/srv.h b/lib9p/srv.h
new file mode 100644
index 0000000..e3623ed
--- /dev/null
+++ b/lib9p/srv.h
@@ -0,0 +1,16 @@
+#ifndef _NET9P_H_
+#define _NET9P_H_
+
+#include "coroutine.h"
+
+struct p9_srvreq;
+
+struct p9_srv {
+ int sockfd;
+ cr_chan_t(p9_srvreq *) reqch;
+};
+
+COROUTINE p9_srv_read_cr(void *_srv);
+COROUTINE p9_srv_write_cr(void *_srv);
+
+#endif /* _NET9P_H_ */