#!/usr/bin/env bash # harness.sh - run a read-only RM tool, capture stdout/stderr, and record the # run into a risc-v-gsp-rm-debugger session directory (blob + events.jsonl line). # # usage: harness.sh [--session DIR] [--name LABEL] -- TOOL [ARGS...] # TOOL is one of: rmtool rmtop rmq # # When --session is given the harness appends a "rm_harness_run" event and # writes the captured streams under /blobs/. Existing session files # are never rewritten; recording is additive. Without --session the tool runs # and streams pass through unchanged. set -euo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ALLOWED="rmtool rmtop rmq" usage() { cat <<'EOF' usage: harness.sh [--session DIR] [--name LABEL] -- TOOL [ARGS...] TOOL is one of: rmtool rmtop rmq (wrapper scripts in this directory). Everything after -- is passed to the tool unchanged. EOF } session="" label="" while [[ $# -gt 0 ]]; do case "$1" in --session) session="${2:?--session needs a directory}" shift 2 ;; --name) label="${2:?--name needs a label}" shift 2 ;; -h | --help) usage exit 0 ;; --) shift break ;; -*) echo "harness.sh: unknown option '$1'" >&2 usage >&2 exit 2 ;; *) break ;; esac done tool="${1:-}" if [[ -z "$tool" ]]; then usage >&2 exit 2 fi shift tool="${tool%.sh}" case " $ALLOWED " in *" $tool "*) ;; *) echo "harness.sh: tool '$tool' is not on the read-only allowlist" \ "($ALLOWED)" >&2 exit 2 ;; esac wrapper="$HERE/$tool.sh" if [[ ! -x "$wrapper" ]]; then echo "harness.sh: wrapper not found: $wrapper" >&2 exit 2 fi argv_json="$(python3 -c 'import json,sys; print(json.dumps(sys.argv[1:]))' \ "$tool" "$@")" out_file="$(mktemp)" err_file="$(mktemp)" trap 'rm -f "$out_file" "$err_file"' EXIT start="$(python3 -c 'import time; print(time.time())')" rc=0 "$wrapper" "$@" >"$out_file" 2>"$err_file" || rc=$? end="$(python3 -c 'import time; print(time.time())')" cat "$out_file" cat "$err_file" >&2 if [[ -n "$session" ]]; then mkdir -p "$session/blobs" stamp="$(date -u +%Y%m%dT%H%M%SZ)" out_rel="blobs/rmharness-${stamp}-${tool}-stdout.txt" err_rel="blobs/rmharness-${stamp}-${tool}-stderr.txt" cp "$out_file" "$session/$out_rel" cp "$err_file" "$session/$err_rel" SESSION="$session" TOOL="$tool" ARGV_JSON="$argv_json" \ RC="$rc" START="$start" END="$end" LABEL="$label" \ OUT_REL="$out_rel" ERR_REL="$err_rel" \ python3 - <<'PY' import hashlib import json import os import time session = os.environ["SESSION"] def blob_info(rel): path = os.path.join(session, rel) with open(path, "rb") as fh: data = fh.read() return {"path": rel, "size": len(data), "sha256": hashlib.sha256(data).hexdigest()} event = { "t": 0.0, "type": "rm_harness_run", "tool": os.environ["TOOL"], "label": os.environ["LABEL"] or None, "argv": json.loads(os.environ["ARGV_JSON"]), "exit": int(os.environ["RC"]), "start": float(os.environ["START"]), "end": float(os.environ["END"]), "duration_s": round(float(os.environ["END"]) - float(os.environ["START"]), 6), "stdout": blob_info(os.environ["OUT_REL"]), "stderr": blob_info(os.environ["ERR_REL"]), "readonly": True, } meta = os.path.join(session, "session.json") if not os.path.exists(meta): with open(meta, "w", encoding="utf-8") as fh: json.dump({"format": "greeneye-session/1", "name": "rmharness", "created": time.time(), "greeneye": "external-rmharness/1"}, fh, indent=1, sort_keys=True) with open(os.path.join(session, "events.jsonl"), "a", encoding="utf-8") as fh: fh.write(json.dumps(event, sort_keys=True) + "\n") print(f"[rmharness] recorded {os.environ['TOOL']} -> {session} " f"({os.environ['OUT_REL']})") PY fi exit "$rc"