#!/usr/bin/env python3 # montauk-syscalls.py - Keep the TCC C API header's syscall numbers in sync # with the canonical syscall ABI. # # Source of truth : programs/include/Api/Syscall.hpp (montauk::abi::SYS_*) # Generated block : programs/include/libc/montauk.h (#define MTK_SYS_*) # # 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. Exits non-zero on any drift. # Run from the build so stale headers fail loudly. # montauk-syscalls.py gen Rewrite the MTK_SYS_* block in montauk.h from the # canonical enum (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") BEGIN_MARKER = "/* @SYSCALLS-BEGIN" END_MARKER = "/* @SYSCALLS-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") 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 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() defs = parse_defines(read(C_HDR)) missing, mismatched, extra = diff(abi, defs) if not (missing or mismatched or extra): print("montauk-syscalls: OK (%d syscalls in sync)" % len(abi)) 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 cmd_gen(): abi = parse_abi() text = read(C_HDR) block = render_block(abi) if BEGIN_MARKER in text and END_MARKER in text: start = text.index(BEGIN_MARKER) end = text.index(END_MARKER) + len(END_MARKER) new = text[:start] + block + text[end:] else: sys.exit("montauk-syscalls: markers not found in %s; add a " "%s ... %s block first" % (C_HDR, BEGIN_MARKER, END_MARKER)) 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())