"""risc-v-gsp-rm-debugger plugin: RM host-oracle harness commands. Registers REPL commands that run the read-only Open-IOV RM tools through the ``rmharness`` wrapper scripts and record each run into the active session: risc-v-gsp-rm-debugger> rmtool gpu list risc-v-gsp-rm-debugger> rmtool mem stats --format=json risc-v-gsp-rm-debugger> rmtop --once risc-v-gsp-rm-debugger> rmq Every run is recorded as a ``rm_harness_run`` event with a captured stdout+stderr blob (via ``ge.recorder.blob``); write/ownership operations are refused and recorded as ``rm_harness_skip``. The plugin is stdlib-only and never touches kernel modules, VFIO, mdevs or the live GPU state. """ from __future__ import annotations import hashlib import os import subprocess import sys import time _TOOL_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) _HARNESS = os.path.join(_TOOL_ROOT, "rmharness") _WRAPPERS = { "rmtool": os.path.join(_HARNESS, "rmtool.sh"), "rmtop": os.path.join(_HARNESS, "rmtop.sh"), "rmq": os.path.join(_HARNESS, "rmq.sh"), } #: arguments that would name a write/ownership operation on any tool. _BLOCKED = frozenset({ "alloc", "allocate", "map", "unmap", "pin", "free", "create", "destroy", "submit", "bind", "unbind", "reset", "start", "stop", "load", "unload", "reboot", "write", "poke", "set", }) def _blocked_arg(args): for arg in args: token = arg.split("=", 1)[0].lstrip("-").lower() if token in _BLOCKED: return arg return None def _record(ge, tool, args, proc, duration): stdout = proc.stdout or "" stderr = proc.stderr or "" payload = (stdout + stderr).encode("utf-8", "replace") blob = None try: blob = ge.recorder.blob( f"rmharness/{tool}-{time.time_ns()}.txt", payload) except Exception: blob = None fields = dict( plugin="rmharness", tool=tool, argv=list(args), exit=proc.returncode, duration_s=round(duration, 6), stdout_sha256=hashlib.sha256( stdout.encode("utf-8", "replace")).hexdigest(), stderr_sha256=hashlib.sha256( stderr.encode("utf-8", "replace")).hexdigest(), stdout_bytes=len(stdout.encode("utf-8", "replace")), stderr_bytes=len(stderr.encode("utf-8", "replace")), blob=blob, readonly=True, ) ge.recorder.event("rm_harness_run", **fields) def _run(ge, tool, args): wrapper = _WRAPPERS[tool] if not os.path.isfile(wrapper): print(f" rmharness: wrapper missing: {wrapper}", file=sys.stderr) ge.recorder.event("rm_harness_error", plugin="rmharness", tool=tool, error="wrapper missing") return 2 bad = _blocked_arg(args) if bad is not None: print(f" rmharness: refused '{bad}': read-only policy " f"(see docs/17-RM-HARNESS.md)", file=sys.stderr) ge.recorder.event("rm_harness_skip", plugin="rmharness", tool=tool, argv=list(args), reason=f"blocked argument {bad!r}") return 2 start = time.monotonic() proc = subprocess.run([wrapper, *args], capture_output=True, text=True) duration = time.monotonic() - start if proc.stdout: print(proc.stdout, end="") if proc.stderr: print(proc.stderr, end="", file=sys.stderr) _record(ge, tool, args, proc, duration) directory = getattr(ge.recorder, "dir", None) if directory: print(f" rmharness: recorded {tool} run -> {directory}") return proc.returncode def register(ge): ge.log("rmharness plugin loaded; tools=" + ",".join(sorted(_WRAPPERS))) def cmd_rmtool(session, args): return _run(ge, "rmtool", args) def cmd_rmtop(session, args): return _run(ge, "rmtop", args) def cmd_rmq(session, args): return _run(ge, "rmq", args) ge.add_command("rmtool", cmd_rmtool, help="rmtool : read-only RM oracle (gpu/engine/mem)") ge.add_command("rmtop", cmd_rmtop, help="rmtop : dashboard; use --once for capture") ge.add_command("rmq", cmd_rmq, help="rmq: existing tools/rmq host oracle (read-only)")