#!/usr/bin/env bash
# elf-trace.sh - run an ELF under strace and produce a files/processes report.
#
#   elf-trace.sh [-o OUTDIR] [-T SECONDS] [-u USER] -- ./binary [args...]
#   elf-trace.sh [-o OUTDIR] -p PID            # attach to a running process
#
# Output (OUTDIR defaults to ../out/<binary>-<timestamp>):
#   strace.log     raw trace, keep it - every claim in the report comes from here
#   REPORT.md      files written / read / probed, process tree, sockets
#   files.csv processes.csv network.csv
set -euo pipefail

HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT="$(dirname "$HERE")"
OUTDIR=""
TIMEOUT=""
RUNAS=""
ATTACH=""
STRSIZE=256

usage() { sed -n '2,12p' "$0"; exit 1; }

while [[ $# -gt 0 ]]; do
  case "$1" in
    -o) OUTDIR="$2"; shift 2 ;;
    -T) TIMEOUT="$2"; shift 2 ;;
    -u) RUNAS="$2"; shift 2 ;;
    -p) ATTACH="$2"; shift 2 ;;
    -s) STRSIZE="$2"; shift 2 ;;
    -h|--help) usage ;;
    --) shift; break ;;
    *) break ;;
  esac
done

command -v strace >/dev/null || { echo "strace not installed - see build/00-install-rpms.sh" >&2; exit 1; }

# strace flags that matter here:
#   -f      follow fork/clone   -> child processes appear in the trace
#   -y -yy  annotate fds with the file/socket they point at
#   -ttt -T timestamps and per-call duration
#   -e trace=%file,%desc,%process,%network  -> the four classes we report on
STRACE_ARGS=(-f -ttt -T -y -yy -s "$STRSIZE"
             -e "trace=%file,%desc,%process,%network"
             -e "signal=none")

if [[ -n "$ATTACH" ]]; then
  NAME="pid${ATTACH}"
  CMDLINE="$(tr '\0' ' ' < /proc/$ATTACH/cmdline 2>/dev/null || echo "pid $ATTACH")"
  CWD="$(readlink /proc/$ATTACH/cwd 2>/dev/null || echo /)"
else
  [[ $# -ge 1 ]] || usage
  BIN="$1"
  [[ -x "$BIN" ]] || { echo "not executable: $BIN" >&2; exit 1; }
  NAME="$(basename "$BIN")"
  CMDLINE="$*"
  CWD="$PWD"
fi

OUTDIR="${OUTDIR:-$ROOT/out/${NAME}-$(date +%Y%m%d-%H%M%S)}"
mkdir -p "$OUTDIR"
LOG="$OUTDIR/strace.log"

echo "== target   : $CMDLINE"
echo "== cwd      : $CWD"
echo "== outdir   : $OUTDIR"

if [[ -n "$ATTACH" ]]; then
  echo "== attaching to pid $ATTACH${TIMEOUT:+ for ${TIMEOUT}s} (Ctrl-C to stop)"
  set +e
  if [[ -n "$TIMEOUT" ]]; then
    timeout -s INT "$TIMEOUT" strace "${STRACE_ARGS[@]}" -o "$LOG" -p "$ATTACH"
  else
    strace "${STRACE_ARGS[@]}" -o "$LOG" -p "$ATTACH"
  fi
  RC=$?
  set -e
else
  # record the static facts about the binary next to the trace
  "$HERE/elf-static.sh" "$BIN" > "$OUTDIR/STATIC.md" 2>/dev/null || true
  set +e
  if [[ -n "$RUNAS" ]]; then
    ${TIMEOUT:+timeout -s TERM "$TIMEOUT"} strace "${STRACE_ARGS[@]}" -o "$LOG" \
        -u "$RUNAS" -- "$@"
  else
    ${TIMEOUT:+timeout -s TERM "$TIMEOUT"} strace "${STRACE_ARGS[@]}" -o "$LOG" -- "$@"
  fi
  RC=$?
  set -e
  echo "== exit code: $RC"
fi

[[ -s "$LOG" ]] || { echo "empty trace - did strace fail? see above" >&2; exit 1; }

"$HERE/parse_strace.py" "$LOG" \
    --cwd "$CWD" --cmd "$CMDLINE" --csv-dir "$OUTDIR" \
    --title "Runtime access report: $NAME" -o "$OUTDIR/REPORT.md"

echo "== report   : $OUTDIR/REPORT.md"
echo "== raw log  : $LOG  ($(wc -l < "$LOG") lines)"
