220 lines
8.4 KiB
Python
Executable File
220 lines
8.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# montauk-syscalls.py - Keep the TCC C API header's syscall numbers and status
|
|
# codes in sync with the canonical syscall ABI.
|
|
#
|
|
# Source of truth : programs/include/Api/Syscall.hpp (montauk::abi::SYS_*)
|
|
# kernel/src/Common/StatusCodes.hpp (enum StatusCodes)
|
|
# Generated blocks: programs/include/libc/montauk.h (#define MTK_SYS_*,
|
|
# #define MTK_STATUS_*)
|
|
#
|
|
# The libc/montauk.h header is a hand-maintained, TCC-compatible C mirror of the
|
|
# syscall surface. Its typed wrapper functions stay hand-written (their argument
|
|
# types are not machine-derivable from the enum), but the raw "#define MTK_SYS_*"
|
|
# number table is mechanical and lives between the @SYSCALLS-BEGIN/END markers,
|
|
# which this script regenerates.
|
|
#
|
|
# Usage:
|
|
# montauk-syscalls.py check Verify every SYS_* has a matching MTK_SYS_* with
|
|
# the same number, and that MTK_STATUS_* matches
|
|
# the kernel enum. Exits non-zero on any drift.
|
|
# Run from the build so stale headers fail loudly.
|
|
# montauk-syscalls.py gen Rewrite the MTK_SYS_* and MTK_STATUS_* blocks in
|
|
# montauk.h (numbers only; wrappers untouched).
|
|
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
REPO_ROOT = os.path.dirname(SCRIPT_DIR)
|
|
ABI_HDR = os.path.join(REPO_ROOT, "programs/include/Api/Syscall.hpp")
|
|
C_HDR = os.path.join(REPO_ROOT, "programs/include/libc/montauk.h")
|
|
STATUS_HDR = os.path.join(REPO_ROOT, "kernel/src/Common/StatusCodes.hpp")
|
|
|
|
BEGIN_MARKER = "/* @SYSCALLS-BEGIN"
|
|
END_MARKER = "/* @SYSCALLS-END */"
|
|
STATUS_BEGIN_MARKER = "/* @STATUS-BEGIN"
|
|
STATUS_END_MARKER = "/* @STATUS-END */"
|
|
|
|
# Canonical SYS_* names intentionally NOT exposed in the C API. Empty by default;
|
|
# add a name here (without the SYS_ prefix) to suppress a kernel-internal syscall.
|
|
EXCLUDE = set()
|
|
|
|
# static constexpr uint64_t SYS_FOO = 42;
|
|
ABI_RE = re.compile(r"constexpr\s+\w+\s+SYS_([A-Z0-9_]+)\s*=\s*(\d+)\s*;")
|
|
# #define MTK_SYS_FOO 42
|
|
DEF_RE = re.compile(r"#define\s+MTK_SYS_([A-Z0-9_]+)\s+(\d+)\b")
|
|
# enum StatusCodes { Name = -3, ... };
|
|
ENUM_RE = re.compile(r"enum\s+StatusCodes\s*\{([^}]*)\}", re.S)
|
|
ENUM_ENTRY_RE = re.compile(r"([A-Za-z_]\w*)\s*=\s*(-?\d+)")
|
|
# #define MTK_STATUS_FOO (-3)
|
|
STATUS_DEF_RE = re.compile(r"#define\s+MTK_STATUS_([A-Z0-9_]+)\s+\(?(-?\d+)\)?")
|
|
|
|
|
|
def read(path):
|
|
with open(path, "r") as f:
|
|
return f.read()
|
|
|
|
|
|
def parse_abi():
|
|
"""Return {NAME: number} for every canonical syscall, minus EXCLUDE."""
|
|
out = {}
|
|
for name, num in ABI_RE.findall(read(ABI_HDR)):
|
|
if name in EXCLUDE:
|
|
continue
|
|
out[name] = int(num)
|
|
if not out:
|
|
sys.exit("montauk-syscalls: no SYS_* entries found in %s" % ABI_HDR)
|
|
return out
|
|
|
|
|
|
def parse_defines(text):
|
|
"""Return {NAME: number} for every MTK_SYS_* define in the C header."""
|
|
return {name: int(num) for name, num in DEF_RE.findall(text)}
|
|
|
|
|
|
def parse_status(path):
|
|
"""Return [(Name, value)] from the StatusCodes enum in path, in order."""
|
|
m = ENUM_RE.search(read(path))
|
|
if not m:
|
|
sys.exit("montauk-syscalls: no enum StatusCodes found in %s" % path)
|
|
return [(n, int(v)) for n, v in ENUM_ENTRY_RE.findall(m.group(1))]
|
|
|
|
|
|
def status_macro(name):
|
|
"""GenericFailure -> GENERIC_FAILURE."""
|
|
return re.sub(r"(?<!^)(?=[A-Z])", "_", name).upper()
|
|
|
|
|
|
def check_status(c_text):
|
|
"""Print drift between the kernel enum and MTK_STATUS_*; return count."""
|
|
canon = parse_status(STATUS_HDR)
|
|
problems = 0
|
|
want = {status_macro(n): v for n, v in canon}
|
|
have = {n: int(v) for n, v in STATUS_DEF_RE.findall(c_text)}
|
|
if want != have:
|
|
problems += 1
|
|
print("montauk-syscalls: DRIFT between Common/StatusCodes.hpp and "
|
|
"MTK_STATUS_* in libc/montauk.h", file=sys.stderr)
|
|
for n in sorted(set(want) | set(have)):
|
|
if want.get(n) != have.get(n):
|
|
print(" MTK_STATUS_%-22s kernel=%s montauk.h=%s"
|
|
% (n, want.get(n), have.get(n)), file=sys.stderr)
|
|
return problems
|
|
|
|
|
|
def diff(abi, defs):
|
|
"""Return (missing, mismatched, extra) describing drift of defs vs abi."""
|
|
missing = sorted(n for n in abi if n not in defs)
|
|
extra = sorted(n for n in defs if n not in abi)
|
|
mismatched = sorted(
|
|
(n, abi[n], defs[n]) for n in abi if n in defs and abi[n] != defs[n]
|
|
)
|
|
return missing, mismatched, extra
|
|
|
|
|
|
def cmd_check():
|
|
abi = parse_abi()
|
|
c_text = read(C_HDR)
|
|
defs = parse_defines(c_text)
|
|
missing, mismatched, extra = diff(abi, defs)
|
|
status_problems = check_status(c_text)
|
|
if not (missing or mismatched or extra):
|
|
if status_problems:
|
|
print("\n Run 'make gen-syscalls' to regenerate MTK_STATUS_*.",
|
|
file=sys.stderr)
|
|
return 1
|
|
print("montauk-syscalls: OK (%d syscalls, %d status codes in sync)"
|
|
% (len(abi), len(parse_status(STATUS_HDR))))
|
|
return 0
|
|
|
|
print("montauk-syscalls: DRIFT between Api/Syscall.hpp and libc/montauk.h",
|
|
file=sys.stderr)
|
|
if missing:
|
|
print(" %d missing from montauk.h:" % len(missing), file=sys.stderr)
|
|
for n in missing:
|
|
print(" SYS_%-22s = %d" % (n, abi[n]), file=sys.stderr)
|
|
if mismatched:
|
|
print(" %d number mismatch(es):" % len(mismatched), file=sys.stderr)
|
|
for n, a, d in mismatched:
|
|
print(" SYS_%-22s abi=%d montauk.h=%d" % (n, a, d),
|
|
file=sys.stderr)
|
|
if extra:
|
|
print(" %d stale (not in ABI):" % len(extra), file=sys.stderr)
|
|
for n in extra:
|
|
print(" MTK_SYS_%s" % n, file=sys.stderr)
|
|
print("\n Run 'make gen-syscalls' to regenerate the MTK_SYS_* number block,",
|
|
file=sys.stderr)
|
|
print(" then add C wrapper functions for any new syscalls by hand.",
|
|
file=sys.stderr)
|
|
return 1
|
|
|
|
|
|
def render_block(abi):
|
|
width = max(len("MTK_SYS_" + n) for n in abi) + 1
|
|
lines = [BEGIN_MARKER,
|
|
" Generated from Api/Syscall.hpp by scripts/montauk-syscalls.py.",
|
|
" Do not edit by hand; run 'make gen-syscalls' to refresh.",
|
|
" Wrapper functions below are hand-written. */"]
|
|
for name in sorted(abi, key=lambda n: abi[n]):
|
|
macro = "MTK_SYS_" + name
|
|
lines.append("#define %-*s%d" % (width, macro, abi[name]))
|
|
lines.append(END_MARKER)
|
|
return "\n".join(lines)
|
|
|
|
|
|
def render_status_block(codes):
|
|
width = max(len("MTK_STATUS_" + status_macro(n)) for n, _ in codes) + 1
|
|
lines = [STATUS_BEGIN_MARKER,
|
|
" Generated from Common/StatusCodes.hpp by scripts/montauk-syscalls.py.",
|
|
" Do not edit by hand; run 'make gen-syscalls' to refresh. */"]
|
|
for name, value in codes:
|
|
macro = "MTK_STATUS_" + status_macro(name)
|
|
lines.append("#define %-*s(%d)" % (width, macro, value))
|
|
lines.append(STATUS_END_MARKER)
|
|
return "\n".join(lines)
|
|
|
|
|
|
def replace_block(text, begin, end, block):
|
|
if begin not in text or end not in text:
|
|
sys.exit("montauk-syscalls: markers not found in %s; add a "
|
|
"%s ... %s block first" % (C_HDR, begin, end))
|
|
start = text.index(begin)
|
|
stop = text.index(end) + len(end)
|
|
return text[:start] + block + text[stop:]
|
|
|
|
|
|
def cmd_gen():
|
|
abi = parse_abi()
|
|
text = read(C_HDR)
|
|
new = replace_block(text, BEGIN_MARKER, END_MARKER, render_block(abi))
|
|
new = replace_block(new, STATUS_BEGIN_MARKER, STATUS_END_MARKER,
|
|
render_status_block(parse_status(STATUS_HDR)))
|
|
|
|
if new == text:
|
|
print("montauk-syscalls: montauk.h already up to date (%d syscalls)"
|
|
% len(abi))
|
|
return 0
|
|
with open(C_HDR, "w") as f:
|
|
f.write(new)
|
|
print("montauk-syscalls: regenerated MTK_SYS_* block (%d syscalls)" % len(abi))
|
|
missing_wrappers = [
|
|
n for n in sorted(abi)
|
|
if not re.search(r"\bMTK_SYS_%s\b" % n, new[new.index(END_MARKER):])
|
|
]
|
|
if missing_wrappers:
|
|
print(" note: %d syscall(s) have a number but no wrapper yet: %s"
|
|
% (len(missing_wrappers),
|
|
", ".join("SYS_" + n for n in missing_wrappers)))
|
|
return 0
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) != 2 or sys.argv[1] not in ("check", "gen"):
|
|
sys.exit("usage: montauk-syscalls.py {check|gen}")
|
|
return cmd_check() if sys.argv[1] == "check" else cmd_gen()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|