#!/usr/bin/env python3
"""Pass-2 cross-check of sys/kern/init_sysent.c against sysproto.h / sysunion.h / syscalls.c.

x86_64 model: every field in a generated args struct is followed by a PAD_ that
rounds the running offset up to a multiple of sizeof(register_t)=8, and no field
type exceeds 8 bytes, so:
    words(struct) == number of fields, sizeof(struct) == 8*nfields.
"""
import re, sys

ROOT = "/home/maxx/dfbsd/dfbsd"
sysent_src = open(f"{ROOT}/sys/kern/init_sysent.c").read()
sysproto_src = open(f"{ROOT}/sys/sys/sysproto.h").read()
sysunion_src = open(f"{ROOT}/sys/sys/sysunion.h").read()
syscalls_src = open(f"{ROOT}/sys/kern/syscalls.c").read()

# ---- parse sysent[] rows: { narg, rsize, call }, /* N = name */
row_re = re.compile(
    r'\{\s*(?:AS\((\w+)\)|(\d+))\s*,\s*(\d+)\s*,\s*\(sy_call_t \*\)(\w+)\s*\}'
    r'\s*,?\s*/\*\s*(\d+)\s*=\s*([^*]*?)\s*\*/')
rows = {}
for m in row_re.finditer(sysent_src):
    asname, lit, rsize, func, idx, name = m.groups()
    rows[int(idx)] = dict(asm=asname, lit=int(lit) if lit else None,
                          rsize=int(rsize), func=func, name=name.strip())
problems = []
SYS_MAXSYSCALL = int(re.search(r'#define\s+SYS_MAXSYSCALL\s+(\d+)',
                               open(f"{ROOT}/sys/sys/syscall.h").read()).group(1))
n_rows = len(rows)
print(f"[i] sysent rows parsed: {n_rows} (0..{max(rows)}); SYS_MAXSYSCALL={SYS_MAXSYSCALL}")
if n_rows != SYS_MAXSYSCALL or max(rows) != SYS_MAXSYSCALL - 1:
    problems.append(f"sysent length {n_rows} != SYS_MAXSYSCALL {SYS_MAXSYSCALL}")
if sorted(rows) != list(range(max(rows) + 1)):
    missing = set(range(max(rows)+1)) - set(rows)
    dups = [i for i in set(rows) if list(rows).count(i) > 1]
    problems.append(f"index gaps/dups: missing={missing}")

# ---- parse sysproto.h structs: name -> (nfields, [(type, name)], is_dummy)
struct_re = re.compile(r'struct\s+(\w+)\s*\{(.*?)\n\};', re.S)
field_re = re.compile(r'^\s*(?:const\s+)?([\w ]+?[\w*])\s+(\w+);')
structs = {}
for m in struct_re.finditer(sysproto_src):
    sname, body = m.group(1), m.group(2)
    fields = []
    for line in body.splitlines():
        line = line.split('/*')[0]
        fm = field_re.match(line)
        if fm and not fm.group(2).endswith('_') and fm.group(1) not in ('char',):
            fields.append((fm.group(1).strip(), fm.group(2)))
    if fields:
        structs[sname] = fields
print(f"[i] sysproto.h structs parsed: {len(structs)}")

# sanity vs known impls
for probe in ('read_args', 'mmap_args', 'lseek_args', 'fork_args', 'nosys_args'):
    if probe in structs:
        print(f"[i]   {probe}: {len(structs[probe])} fields -> {[f[1] for f in structs[probe]]}")

# ---- parse sysunion members
union_members = set(re.findall(r'struct\s+(\w+)\s+\w+;', sysunion_src))
print(f"[i] sysunion.h members: {len(union_members)}")

# ---- parse syscallnames[]
name_re = re.compile(r'"([^"]*)",?\s*/\*\s*(\d+)')
names = {int(m.group(2)): m.group(1) for m in name_re.finditer(syscalls_src)}
print(f"[i] syscallnames[] entries: {len(names)} (max {max(names)})")
if len(names) != SYS_MAXSYSCALL or max(names) != SYS_MAXSYSCALL - 1:
    problems.append(f"syscallnames length {len(names)} != SYS_MAXSYSCALL")

# ---- per-row checks
maxwords = 0
for idx in sorted(rows):
    r = rows[idx]
    # name alignment (informational for alias rows)
    # 1. AS(x_args) struct must exist and be a sysunion member
    if r["asm"]:
        sname = r["asm"]
        if sname not in structs:
            problems.append(f"[{idx}] AS({sname}): struct not found in sysproto.h")
            continue
        words = len(structs[sname])
        maxwords = max(maxwords, words)
        if sname not in union_members:
            problems.append(f"[{idx}] AS({sname}) NOT a member of union sysunion "
                            f"(extargs copyin dest) -> potential stack overflow")
        # struct must be all-dummy or >0 fields ok; AS count equals words by construction
    # 2. literal 0 narg with a real (non-nosys/lkm) func: args struct must be dummy-only
    elif r["lit"] == 0 and r["func"] not in ("sys_nosys", "sys_lkmnosys"):
        # find the args struct the impl expects: func sys_foo -> struct foo_args (with manual fixes)
        base = r["func"].removeprefix("sys_")
        cand = f"{base}_args"
        # table aliases: wait4 uses wait_args etc.
        alias = {"wait4": "wait_args", "___sysctl": "sysctl_args",
                 "___getcwd": "__getcwd_args", "__getrlimit": "__getrlimit_args",
                 "__setrlimit": "__setrlimit_args", "___realpath": "__realpath_args",
                 "xsyscall": "nosys_args"}.get(base, cand)
        st = structs.get(alias)
        if st is None:
            problems.append(f"[{idx}] {r['func']}: narg=0 but args struct {alias} unknown")
        elif not (len(st) == 1 and st[0][1] == "dummy"):
            problems.append(f"[{idx}] {r['func']}: narg=0 in table but args struct "
                            f"{alias} has fields {[f[1] for f in st]} -> impl reads "
                            f"uninitialized/absent args")
    # 3. rsize sanity
    if r["rsize"] not in (4, 8):
        problems.append(f"[{idx}] weird rsize {r['rsize']}")

print(f"[i] max args words across table: {maxwords}")

# ---- union capacity: largest member struct
umax = max((len(structs[m]) for m in union_members if m in structs), default=0)
print(f"[i] union sysunion capacity: {umax} words (largest member)")
if maxwords > umax:
    problems.append(f"table max narg {maxwords} > union capacity {umax} -> "
                    f"copyin overflows sysmsg.extargs on kernel stack")

# ---- name table consistency: every implemented row's comment name matches syscallnames
for idx in sorted(rows):
    nm = rows[idx]["name"].strip()
    if idx in names:
        pass  # informational only; aliases (netbsd_*) differ by design

# ---- impls with rsize==8: verify they produce 64-bit results
print("\n[i] rsize==8 entries:", [ (i, rows[i]["func"]) for i in sorted(rows) if rows[i]["rsize"] == 8 ])

print("\n==== PROBLEMS ====" if problems else "\n==== NO STRUCTURAL PROBLEMS ====")
for p in problems:
    print(" !", p)
