#!/usr/bin/env bash
# elf-fanotify.sh - system-wide file access while a command runs (fatrace).
#
#   elf-fanotify.sh [-o OUTDIR] -- ./binary [args...]
#
# fanotify sees opens/reads/writes on whole mounts, so it catches file access
# made by *any* process the target talks to - a helper daemon, a shell it
# spawns, a service it pokes over a socket. strace cannot see those.
# Trade-off: it is system-wide, so you must filter out unrelated noise.
set -eu   # no pipefail: several report pipes end in head(1)
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"; ROOT="$(dirname "$HERE")"
FATRACE="${FATRACE:-$ROOT/build/bin/fatrace}"
command -v fatrace >/dev/null && FATRACE="$(command -v fatrace)"
[[ -x "$FATRACE" ]] || { echo "fatrace not built. Run: build/10-build-sources.sh fatrace" >&2; exit 1; }
[[ $EUID -eq 0 ]] || { echo "fanotify needs root (CAP_SYS_ADMIN)" >&2; exit 1; }

OUTDIR=""
while [[ $# -gt 0 ]]; do
  case "$1" in -o) OUTDIR="$2"; shift 2 ;; --) shift; break ;; *) break ;; esac
done
[[ $# -ge 1 ]] || { sed -n '2,12p' "$0"; exit 1; }
NAME="$(basename "$1")"
OUTDIR="${OUTDIR:-$ROOT/out/${NAME}-fanotify-$(date +%Y%m%d-%H%M%S)}"
mkdir -p "$OUTDIR"
RAW="$OUTDIR/fatrace.log"

"$FATRACE" --timestamp > "$RAW" 2>/dev/null &
FPID=$!
sleep 0.3
echo "== fatrace pid $FPID, running target..."
set +e
"$@" &
TPID=$!
wait "$TPID"; RC=$?
set -e
sleep 0.5
kill "$FPID" 2>/dev/null || true
wait "$FPID" 2>/dev/null || true

if [[ ! -s "$RAW" ]]; then
  cat >&2 <<'TXT'
fanotify recorded no events at all. That is an environment limit, not a result:
  * inside a container the root filesystem is usually overlayfs, which fanotify
    cannot mark - run this on the host, or bind-mount a real filesystem
  * CAP_SYS_ADMIN is required even for root in a restricted container
  * some hardened kernels disable fanotify permission events entirely
Use tools/elf-trace.sh or tools/elf-audit.sh here instead.
TXT
  exit 2
fi

{
echo "# System-wide file activity while \`$*\` ran"
echo
echo "Exit code: $RC.  Raw log: \`$RAW\` ($(wc -l < "$RAW") events)"
echo
echo "## Events from the target itself (comm = $NAME, pid $TPID)"
echo
echo "fanotify reports only \`comm(pid)\`, not the parent, so children appear"
echo "under their own names - cross-reference them with the process tree in"
echo "REPORT.md from elf-trace.sh."
echo '```'
grep -E "(^|[[:space:]])${NAME}\(" "$RAW" | head -200 || echo "(none matched)"
echo '```'
echo "## Every process that touched a file during the window"
echo '```'
awk '{print $1}' "$RAW" | sort | uniq -c | sort -rn | head -30
echo '```'
echo "## Files written by anyone during the window (W/ CW flags)"
echo '```'
awk '$2 ~ /W/ {print $0}' "$RAW" | awk '{print $NF}' | sort -u | head -80
echo '```'
} > "$OUTDIR/FANOTIFY.md"
echo "== report: $OUTDIR/FANOTIFY.md"
