From 71e1a86a033c380f85dd300d788af63bfef25bab Mon Sep 17 00:00:00 2001 From: "Luke T. Shumaker" Date: Thu, 26 Sep 2024 19:36:54 -0600 Subject: wip reorg --- lib9p/.editorconfig | 3 + lib9p/.gitignore | 2 + lib9p/9P2000.L.txt | 56 ++++++ lib9p/9P2000.e.txt | 16 ++ lib9p/9P2000.txt | 82 +++++++++ lib9p/9P2000.u.txt | 20 +++ lib9p/9p.h | 15 ++ lib9p/defs.c | 89 ++++++++++ lib9p/defs.gen | 456 ++++++++++++++++++++++++++++++++++++++++++++++++ lib9p/defs.h | 74 ++++++++ lib9p/internal.h | 139 +++++++++++++++ lib9p/linux-errno.h.gen | 34 ++++ lib9p/misc.txt | 24 +++ lib9p/srv.c | 194 ++++++++++++++++++++ lib9p/srv.h | 16 ++ 15 files changed, 1220 insertions(+) create mode 100644 lib9p/.editorconfig create mode 100644 lib9p/.gitignore create mode 100644 lib9p/9P2000.L.txt create mode 100644 lib9p/9P2000.e.txt create mode 100644 lib9p/9P2000.txt create mode 100644 lib9p/9P2000.u.txt create mode 100644 lib9p/9p.h create mode 100644 lib9p/defs.c create mode 100755 lib9p/defs.gen create mode 100644 lib9p/defs.h create mode 100644 lib9p/internal.h create mode 100755 lib9p/linux-errno.h.gen create mode 100644 lib9p/misc.txt create mode 100644 lib9p/srv.c create mode 100644 lib9p/srv.h (limited to 'lib9p') 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 +# 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 +# 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 +# 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 +# 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 + * 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 + * SPDX-Licence-Identifier: AGPL-3.0-or-later + */ + +#include /* for PRIu{n} */ +#include /* for va_* */ +#include /* for vsnprintf() */ +#include /* 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 +# 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{re_membername})\\*\\()?(?P{re_membername})\\[(?P.*)\\]\\)?" +) + + +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[^"]+)"' +re_structspec = ( + r'(?:(?P[0-9]+)/)?(?P\S+)\s*(?P\+?=)\s*"(?P[^"]*)"' +) +re_structspec_cont = r'"(?P[^"]*)"' + + +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 +#include +#include /* 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 + * SPDX-Licence-Identifier: AGPL-3.0-or-later + */ + +#ifndef _9P_DEFS_H_ +#define _9P_DEFS_H_ + +#include + +#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 + * SPDX-Licence-Identifier: AGPL-3.0-or-later + */ + +#ifndef _9P_INTERNAL_H_ +#define _9P_INTERNAL_H_ + +#include +#include +#include + +#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 + +#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_ */ -- cgit v1.2.3-2-g168b