summaryrefslogtreecommitdiff
path: root/lib9p
diff options
context:
space:
mode:
authorLuke T. Shumaker <lukeshu@lukeshu.com>2024-10-02 11:21:55 -0600
committerLuke T. Shumaker <lukeshu@lukeshu.com>2024-10-02 11:21:55 -0600
commitada828fc3eaf9891e1bbb6503106d36ef53b6c8a (patch)
tree897cb32a2656f72b5bbafa250fffc97895f74785 /lib9p
parent205f7479c30d13bb78c0949148d975e0975655f9 (diff)
wip lib9p bitfields
Diffstat (limited to 'lib9p')
-rw-r--r--lib9p/9P2000.txt35
-rw-r--r--lib9p/9P2000.u.txt12
-rw-r--r--lib9p/include/lib9p/_types.h6
-rw-r--r--lib9p/types.c6
-rwxr-xr-xlib9p/types.gen204
5 files changed, 184 insertions, 79 deletions
diff --git a/lib9p/9P2000.txt b/lib9p/9P2000.txt
index 5f93cdf..3f1f10a 100644
--- a/lib9p/9P2000.txt
+++ b/lib9p/9P2000.txt
@@ -30,8 +30,39 @@ 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]"
+bitfield qid_type 8
+ 7/DIR
+ 6/APPEND
+ 5/EXCL
+ # QTMOUNT has been around in Plan 9 forever, but is
+ # undocumented, and is explicitly excluded from the 9P2000
+ # draft RFC. As I understand it, QTMOUNT indicates that the
+ # file is mounted by the kernel as a 9P transport; that the
+ # kernel has a lock on doing I/O on it, so userspace can't do
+ # I/O on it.
+ 4/_PLAN9_MOUNT
+ 3/AUTH
+ # Fun historical fact: QTTMP was a relatively late addition to
+ # Plan 9, in 2003-12.
+ 2/TMP
+ #1/unused
+ FILE=0
+
+# uniQue IDentification - "two files on the same server hierarchy are
+# the same if and only if their qids are the same"
+#
+# - "path" is a unique uint64_t that does most of the work in the
+# above statement about files being the same if their QIDs are the
+# same; " If a file is deleted and recreated with the same name in
+# the same directory, the old and new path components of the qids
+# should be different"
+#
+# - "vers" "is a version number for a file; typically, it is
+# incremented every time the file is modified.
+#
+# - "type" indicates "whether the file is a directory, append-only
+# file, etc."; is an instance of the qid_type bitfield.
+qid = "type[qid_type] vers[4] path[8]"
# stat (TODO)
stat = "stat_size[2]"
diff --git a/lib9p/9P2000.u.txt b/lib9p/9P2000.u.txt
index 1fa71fb..a1dbcfb 100644
--- a/lib9p/9P2000.u.txt
+++ b/lib9p/9P2000.u.txt
@@ -19,16 +19,8 @@ Tauth += "n_uname[4]"
Rerror += "errno[4]"
-# # 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
-#
+qid_type += 1/SYMLINK
+
# DMDIR = 1<<31
# DMAPPEND = 1<<30
# DMEXCL = 1<<29
diff --git a/lib9p/include/lib9p/_types.h b/lib9p/include/lib9p/_types.h
index 94accc0..cd1ded5 100644
--- a/lib9p/include/lib9p/_types.h
+++ b/lib9p/include/lib9p/_types.h
@@ -30,9 +30,9 @@ struct lib9p_s {
};
struct lib9p_qid {
- uint8_t type;
- uint32_t vers;
- uint64_t path;
+ lib9p_qid_type_t type;
+ uint32_t vers;
+ uint64_t path;
};
struct lib9p_stat {
diff --git a/lib9p/types.c b/lib9p/types.c
index 19ed322..4854ed4 100644
--- a/lib9p/types.c
+++ b/lib9p/types.c
@@ -344,7 +344,7 @@ static ALWAYS_INLINE bool checksize_s(struct _checksize_ctx *ctx) {
}
static ALWAYS_INLINE bool checksize_qid(struct _checksize_ctx *ctx) {
- return checksize_1(ctx)
+ return checksize_qid_type(ctx)
|| checksize_4(ctx)
|| checksize_8(ctx);
}
@@ -571,7 +571,7 @@ static ALWAYS_INLINE void unmarshal_s(struct _unmarshal_ctx *ctx, struct lib9p_s
static ALWAYS_INLINE void unmarshal_qid(struct _unmarshal_ctx *ctx, struct lib9p_qid *out) {
memset(out, 0, sizeof(*out));
- unmarshal_1(ctx, &out->type);
+ unmarshal_qid_type(ctx, &out->type);
unmarshal_4(ctx, &out->vers);
unmarshal_8(ctx, &out->path);
}
@@ -860,7 +860,7 @@ static ALWAYS_INLINE bool marshal_s(struct _marshal_ctx *ctx, struct lib9p_s *va
}
static ALWAYS_INLINE bool marshal_qid(struct _marshal_ctx *ctx, struct lib9p_qid *val) {
- return marshal_1(ctx, &val->type)
+ return marshal_qid_type(ctx, &val->type)
|| marshal_4(ctx, &val->vers)
|| marshal_8(ctx, &val->path);
}
diff --git a/lib9p/types.gen b/lib9p/types.gen
index 9946e35..1a2d45a 100755
--- a/lib9p/types.gen
+++ b/lib9p/types.gen
@@ -8,7 +8,7 @@
import enum
import os.path
import re
-from typing import Callable
+from typing import Callable, Sequence
# This strives to be "general-purpose" in that it just acts on the
# *.txt inputs; but (unfortunately?) there are a few special-cases in
@@ -32,6 +32,16 @@ class Atom(enum.Enum):
return self.value
+class Bitfield:
+ name: str
+ bits: list[str]
+ aliases: dict[str, str]
+
+ @property
+ def static_size(self) -> int | None:
+ return int((len(self.bits) + 7) / 8)
+
+
# `msgid/structname = "member1 member2..."`
# `structname = "member1 member2..."`
# `structname += "member1 member2..."`
@@ -60,7 +70,7 @@ class Struct:
class Member:
cnt: str | None = None
name: str
- typ: Atom | Struct
+ typ: Atom | Bitfield | Struct
ver: set[str]
@property
@@ -77,7 +87,10 @@ re_memberspec = (
def parse_members(
- ver: str, env: dict[str, Atom | Struct], existing: list[Member], specs: str
+ ver: str,
+ env: dict[str, Atom | Bitfield | Struct],
+ existing: list[Member],
+ specs: str,
) -> list[Member]:
ret = existing
for spec in specs.split():
@@ -112,23 +125,26 @@ re_import = r"from\s+(?P<file>\S+)\s+import\s+(?P<syms>\S+(?:\s*,\s*\S+)*)\s*"
re_structspec = (
r'(?:(?P<msgid>[0-9]+)/)?(?P<name>\S+)\s*(?P<op>\+?=)\s*"(?P<members>[^"]*)"'
)
-re_structspec_cont = r'"(?P<members>[^"]*)"'
+re_structspec_cont = r'\s+"(?P<members>[^"]*)"'
+re_bitfieldspec = r"bitfield\s+(?P<name>\S+)\s+(?P<size>[0-9]+)"
+re_bitfieldspec_bit = r"(?:\s+|(?P<bitfield>\S+)\s*\+=\s*)(?P<bit>[0-9]+)/(?P<name>\S+)"
+re_bitfieldspec_alias = r"(?:\s+|(?P<bitfield>\S+)\s*\+=\s*)(?P<name>\S+)=(?P<val>.*)"
def parse_file(
- filename: str, get_include: Callable[[str], tuple[str, list[Struct]]]
-) -> tuple[str, list[Struct]]:
+ filename: str, get_include: Callable[[str], tuple[str, list[Bitfield | Struct]]]
+) -> tuple[str, list[Bitfield | Struct]]:
version: str | None = None
- env: dict[str, Atom | Struct] = {
+ env: dict[str, Atom | Bitfield | Struct] = {
"1": Atom.u8,
"2": Atom.u16,
"4": Atom.u32,
"8": Atom.u64,
}
with open(filename, "r") as fh:
- prev: Struct | None = None
+ prev: Struct | Bitfield | None = None
for line in fh:
- line = line.split("#", 1)[0].strip()
+ line = line.split("#", 1)[0].rstrip()
if not line:
continue
if m := re.fullmatch(re_version, line):
@@ -138,17 +154,18 @@ def parse_file(
elif m := re.fullmatch(re_import, line):
if not version:
raise SyntaxError("must have exactly 1 version line")
- other_version, other_structs = get_include(m.group("file"))
+ other_version, other_typs = get_include(m.group("file"))
for symname in m.group("syms").split(sep=","):
symname = symname.strip()
- for struct in other_structs:
- if struct.name == symname or symname == "*":
- if struct.msgid:
- struct.msgver.add(version)
- for member in struct.members:
- if other_version in member.ver:
- member.ver.add(version)
- env[struct.name] = struct
+ for typ in other_typs:
+ if typ.name == symname or symname == "*":
+ if isinstance(typ, Struct):
+ if typ.msgid:
+ typ.msgver.add(version)
+ for member in typ.members:
+ if other_version in member.ver:
+ member.ver.add(version)
+ env[typ.name] = typ
elif m := re.fullmatch(re_structspec, line):
if not version:
raise SyntaxError("must have exactly 1 version line")
@@ -172,7 +189,7 @@ def parse_file(
_struct = env[m.group("name")]
if not isinstance(_struct, Struct):
raise NameError(
- f"Type {repr(m.group('name'))} is not a struct"
+ f"Type {repr(_struct.name)} is not a struct"
)
struct = _struct
struct.members = parse_members(
@@ -180,27 +197,82 @@ def parse_file(
)
prev = struct
elif m := re.fullmatch(re_structspec_cont, line):
- if not prev:
- raise SyntaxError("continuation line must come after a struct line")
+ if not isinstance(prev, Struct):
+ raise SyntaxError(
+ "struct-continuation line must come after a struct line"
+ )
assert version
prev.members = parse_members(
version, env, prev.members, m.group("members")
)
+ elif m := re.fullmatch(re_bitfieldspec, line):
+ bf = Bitfield()
+ bf.name = m.group("name")
+ bf.bits = int(m.group("size")) * [""]
+ bf.aliases = {}
+ env[bf.name] = bf
+ prev = bf
+ elif m := re.fullmatch(re_bitfieldspec_bit, line):
+ if m.group("bitfield"):
+ if m.group("bitfield") not in env:
+ raise NameError(f"Unknown bitfield {repr(m.group('bitfield'))}")
+ _bf = env[m.group("bitfield")]
+ if not isinstance(_bf, Bitfield):
+ raise NameError(f"Type {repr(_bf.name)} is not a bitfield")
+ bf = _bf
+ else:
+ if not isinstance(prev, Bitfield):
+ raise SyntaxError(
+ "bitfield-continuation line must come after a bitfield line"
+ )
+ bf = prev
+ bit = int(m.group("bit"))
+ name = m.group("name")
+ if bit < 0 or bit >= len(bf.bits):
+ raise ValueError(f"{bf.name}: bit {bit} is out-of-bounds")
+ if bf.bits[bit]:
+ raise ValueError(f"{bf.name}: bit {bit} already assigned")
+ if name in bf.aliases:
+ raise ValueError(f"{bf.name}: name {name} already assigned")
+ bf.bits[bit] = name
+ bf.aliases[name] = ""
+ elif m := re.fullmatch(re_bitfieldspec_alias, line):
+ if m.group("bitfield"):
+ if m.group("bitfield") not in env:
+ raise NameError(f"Unknown bitfield {repr(m.group('bitfield'))}")
+ _bf = env[m.group("bitfield")]
+ if not isinstance(_bf, Bitfield):
+ raise NameError(f"Type {repr(_bf.name)} is not a bitfield")
+ bf = _bf
+ else:
+ if not isinstance(prev, Bitfield):
+ raise SyntaxError(
+ "bitfield-continuation line must come after a bitfield line"
+ )
+ bf = prev
+ name = m.group("name")
+ val = m.group("val")
+ if name in bf.aliases:
+ raise ValueError(f"{bf.name}: name {name} already assigned")
+ bf.aliases[name] = val
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
+ typs = [x for x in env.values() if not isinstance(x, Atom)]
+
+ return version, typs
# Generate C ###################################################################
-def c_typename(idprefix: str, typ: Atom | Struct) -> str:
+def c_typename(idprefix: str, typ: Atom | Bitfield | Struct) -> str:
match typ:
case Atom():
return f"uint{typ.value*8}_t"
+ case Bitfield():
+ return f"{idprefix}{typ.name}_t"
case Struct():
if typ.msgid is not None:
return f"struct {idprefix}msg_{typ.name}"
@@ -227,7 +299,25 @@ def c_vercond(idprefix: str, versions: set[str]) -> str:
)
-def gen_h(idprefix: str, versions: set[str], structs: list[Struct]) -> str:
+def just_structs_all(typs: list[Bitfield | Struct]) -> Sequence[Struct]:
+ return list(typ for typ in typs if isinstance(typ, Struct))
+
+
+def just_structs_nonmsg(typs: list[Bitfield | Struct]) -> Sequence[Struct]:
+ return list(typ for typ in typs if isinstance(typ, Struct) and typ.msgid is None)
+
+
+def just_structs_msg(typs: list[Bitfield | Struct]) -> Sequence[Struct]:
+ return list(
+ typ for typ in typs if isinstance(typ, Struct) and typ.msgid is not None
+ )
+
+
+def just_bitfields(typs: list[Bitfield | Struct]) -> Sequence[Bitfield]:
+ return list(typ for typ in typs if isinstance(typ, Bitfield))
+
+
+def gen_h(idprefix: str, versions: set[str], typs: list[Bitfield | Struct]) -> str:
guard = "_LIB9P__TYPES_H_"
ret = f"""/* Generated by `{' '.join(sys.argv)}`. DO NOT EDIT! */
@@ -255,10 +345,7 @@ enum {idprefix}version {{
ret += """
/* non-message structs ********************************************************/
"""
- for struct in structs:
- if struct.msgid is not None:
- continue
-
+ for struct in just_structs_nonmsg(typs):
all_the_same = len(struct.members) == 0 or all(
m.ver == struct.members[0].ver for m in struct.members
)
@@ -280,10 +367,8 @@ enum {idprefix}version {{
"""
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
+ namewidth = max(len(msg.name) for msg in just_structs_msg(typs))
+ for msg in just_structs_msg(typs):
ret += f"\t{idprefix.upper()}TYP_{msg.name.ljust(namewidth)} = {msg.msgid},"
if comment := c_vercomment(msg.msgver):
ret += " " + comment
@@ -292,10 +377,7 @@ enum {idprefix}version {{
ret += "\n"
ret += f"const char *{idprefix}msg_type_str(enum {idprefix}msg_type);\n"
- for msg in structs:
- if msg.msgid is None:
- continue
-
+ for msg in just_structs_msg(typs):
ret += "\n"
if comment := c_vercomment(msg.msgver):
ret += comment + "\n"
@@ -324,7 +406,7 @@ enum {idprefix}version {{
return ret
-def gen_c(idprefix: str, versions: set[str], structs: list[Struct]) -> str:
+def gen_c(idprefix: str, versions: set[str], typs: list[Bitfield | Struct]) -> str:
ret = f"""/* Generated by `{' '.join(sys.argv)}`. DO NOT EDIT! */
#include <assert.h>
@@ -362,9 +444,9 @@ const char *{idprefix}version_str(enum {idprefix}version ver) {{
static const char *msg_type_strs[0x100] = {{
"""
id2name: dict[int, str] = {}
- for msg in structs:
- if msg.msgid is not None:
- id2name[msg.msgid] = msg.name
+ for msg in just_structs_msg(typs):
+ assert msg.msgid
+ id2name[msg.msgid] = msg.name
for n in range(0, 0x100):
ret += '\t[0x{:02X}] = "{}",\n'.format(n, id2name.get(n, "0x{:02X}".format(n)))
ret += "};\n"
@@ -412,7 +494,7 @@ static ALWAYS_INLINE bool _checksize_list(struct _checksize_ctx *ctx,
#define checksize_4(ctx) _checksize_net(ctx, 4)
#define checksize_8(ctx) _checksize_net(ctx, 8)
"""
- for struct in structs:
+ for struct in just_structs_all(typs):
inline = " ALWAYS_INLINE" if struct.msgid is None else " FLATTEN"
argfn = used if struct.members else unused
ret += "\n"
@@ -495,7 +577,7 @@ static ALWAYS_INLINE void unmarshal_8(struct _unmarshal_ctx *ctx, uint64_t *out)
ctx->net_offset += 8;
}
"""
- for struct in structs:
+ for struct in just_structs_all(typs):
inline = " ALWAYS_INLINE" if struct.msgid is None else " FLATTEN"
argfn = used if struct.members else unused
ret += "\n"
@@ -567,7 +649,7 @@ static ALWAYS_INLINE bool marshal_8(struct _marshal_ctx *ctx, uint64_t *val) {
return false;
}
"""
- for struct in structs:
+ for struct in just_structs_all(typs):
inline = " ALWAYS_INLINE" if struct.msgid is None else " FLATTEN"
argfn = used if struct.members else unused
ret += "\n"
@@ -616,14 +698,14 @@ struct _vtable_version _{idprefix}vtables[{c_verenum(idprefix, 'NUM')}] = {{
"""
ret += f"\t[{c_verenum(idprefix, 'unknown')}] = {{ .msgs = {{\n"
- for msg in structs:
+ for msg in just_structs_msg(typs):
if msg.name in ["Tversion", "Rversion", "Rerror"]: # SPECIAL
ret += f"\t\t_MSG({msg.name}),\n"
ret += "\t}},\n"
for ver in sorted(versions):
ret += f"\t[{c_verenum(idprefix, ver)}] = {{ .msgs = {{\n"
- for msg in structs:
+ for msg in just_structs_msg(typs):
if ver not in msg.msgver:
continue
ret += f"\t\t_MSG({msg.name}),\n"
@@ -638,32 +720,32 @@ struct _vtable_version _{idprefix}vtables[{c_verenum(idprefix, 'NUM')}] = {{
class Parser:
- cache: dict[str, tuple[str, list[Struct]]] = {}
+ cache: dict[str, tuple[str, list[Bitfield | Struct]]] = {}
- def parse_file(self, filename: str) -> tuple[str, list[Struct]]:
+ def parse_file(self, filename: str) -> tuple[str, list[Bitfield | Struct]]:
filename = os.path.normpath(filename)
if filename not in self.cache:
- def get_include(other_filename: str) -> tuple[str, list[Struct]]:
+ def get_include(other_filename: str) -> tuple[str, list[Bitfield | Struct]]:
return self.parse_file(os.path.join(filename, "..", other_filename))
self.cache[filename] = parse_file(filename, get_include)
return self.cache[filename]
- def all(self) -> tuple[set[str], list[Struct]]:
+ def all(self) -> tuple[set[str], list[Bitfield | Struct]]:
ret_versions: set[str] = set()
- ret_structs: dict[str, Struct] = {}
- for version, structs in self.cache.values():
+ ret_typs: dict[str, Bitfield | Struct] = {}
+ for version, typs in self.cache.values():
if version in ret_versions:
raise ValueError(f"duplicate protocol version {repr(version)}")
ret_versions.add(version)
- for struct in structs:
- if struct.name in ret_structs:
- if struct != ret_structs[struct.name]:
- raise ValueError(f"duplicate struct name {repr(struct.name)}")
+ for typ in typs:
+ if typ.name in ret_typs:
+ if typ != ret_typs[typ.name]:
+ raise ValueError(f"duplicate type name {repr(typ.name)}")
else:
- ret_structs[struct.name] = struct
- return ret_versions, list(ret_structs.values())
+ ret_typs[typ.name] = typ
+ return ret_versions, list(ret_typs.values())
if __name__ == "__main__":
@@ -674,9 +756,9 @@ if __name__ == "__main__":
parser = Parser()
for txtname in sys.argv[1:]:
parser.parse_file(txtname)
- versions, structs = parser.all()
+ versions, typs = parser.all()
outdir = os.path.normpath(os.path.join(sys.argv[0], ".."))
with open(os.path.join(outdir, "include/lib9p/_types.h"), "w") as fh:
- fh.write(gen_h("lib9p_", versions, structs))
+ fh.write(gen_h("lib9p_", versions, typs))
with open(os.path.join(outdir, "types.c"), "w") as fh:
- fh.write(gen_c("lib9p_", versions, structs))
+ fh.write(gen_c("lib9p_", versions, typs))