#!/usr/bin/env bash
# shim-guard.sh — shared re-entry bound and self-rejecting resolution for the PATH
# shims in ../bin (_git-guard-shim.sh, _cpu-guard-shim.sh, _tmpjail-shim.sh,
# _kill-guard-shim.sh). Each wraps a command by its own name, so a resolution that
# lands on another copy of a shim makes the wrapper invoke itself without bound —
# that is the fork loop this file exists to make structurally impossible.
#
# Both functions use builtins only: no fork happens before the bound is in place.

# shim_guard_enter <name> — exits non-zero past the bound; sets SHIM_REENTRY=1 when an
# ancestor already applied the shim for <name>, meaning the caller must exec the real
# binary immediately and do no further work. The applied flag survives the exec into the
# real binary; the depth counter must not, so shim_guard_clear drops it at that boundary.
shim_guard_enter() {
  local name="$1" key var applied depth
  key="${name//[^A-Za-z0-9]/_}"
  var="_OD_SHIM_DEPTH_$key"
  applied="_OD_SHIM_APPLIED_$key"
  depth="${!var:-0}"
  [[ "$depth" =~ ^[0-9]+$ ]] || depth=0
  if ((depth >= 2)); then
    echo "$name: PATH shim re-entered at depth $depth — refusing (its resolution points back at a shim)" >&2
    exit 79
  fi
  export "$var=$((depth + 1))"
  if [[ -n "${!applied:-}" ]]; then SHIM_REENTRY=1; else SHIM_REENTRY=0; fi
  export "$applied=1"
}

# shim_guard_clear <name> — drops the re-entry counter for <name>. Call immediately before
# exec'ing a resolved real binary: past that point the frames belong to the real command's
# own process tree, and a counter that kept climbing would refuse legitimate nesting.
shim_guard_clear() {
  local name="$1"
  unset "_OD_SHIM_DEPTH_${name//[^A-Za-z0-9]/_}"
}

# shim_is_shim <path> — 0 when the file is a script carrying OD_PATH_SHIM_MARKER, and also
# when it cannot be read: an unreadable candidate must never be resolved as the real binary.
shim_is_shim() {
  local f="$1" line i=0
  [[ -r "$f" ]] || return 0
  IFS= read -r line <"$f" 2>/dev/null || return 1
  [[ "$line" == '#!'* ]] || return 1
  while IFS= read -r line; do
    if [[ "$line" == *OD_PATH_SHIM_MARKER* ]]; then return 0; fi
    i=$((i + 1))
    if ((i >= 60)); then break; fi
  done <"$f"
  return 1
}

# shim_pin_read <name> — reads the install-time pin for <name>. Sets SHIM_PIN_REAL to the
# pinned path and SHIM_PIN_TRIED to the pin files it looked at; rc 1 when no pin holds a
# usable absolute executable. $HOME wins so a test or a sandbox can supply its own pin; the
# passwd home for $EUID is the fallback, so a process running under a constructed $HOME
# still finds the machine's pin. OD_SHIM_PIN_FILE names the only pin to consider, for
# fixtures that must reach the no-pin branch. Builtins only: no fork.
#
# A pin naming a shim is skipped like an unusable one. Resolving it instead makes the
# wrapper exec a wrapper: the depth counter is cleared at each hand-off to the "real"
# binary, so that pair execs each other forever and the command never returns.
shim_pin_read() {
  local name="$1" h pin real u p uid gid gecos home sh pw_home=""
  local -a homes=()
  SHIM_PIN_REAL=""
  SHIM_PIN_TRIED=""
  while IFS=: read -r u p uid gid gecos home sh; do
    if [[ "$uid" == "$EUID" && "$home" == /* ]]; then
      pw_home="$home"
      break
    fi
  done </etc/passwd 2>/dev/null
  if [[ -n "${OD_SHIM_PIN_FILE:-}" ]]; then
    homes=()
  else
    [[ -n "${HOME:-}" ]] && homes+=("$HOME")
    [[ -n "$pw_home" && "$pw_home" != "${HOME:-}" ]] && homes+=("$pw_home")
  fi
  for h in "" ${homes[@]+"${homes[@]}"}; do
    if [[ -z "$h" ]]; then
      [[ -n "${OD_SHIM_PIN_FILE:-}" ]] || continue
      pin="$OD_SHIM_PIN_FILE"
    else
      pin="$h/.local/state/overdeck/shim-real/$name"
    fi
    if [[ -n "$SHIM_PIN_TRIED" ]]; then SHIM_PIN_TRIED+=", $pin"; else SHIM_PIN_TRIED="$pin"; fi
    [[ -r "$pin" ]] || continue
    real=""
    IFS= read -r real <"$pin" || true
    [[ -n "$real" && "$real" == /* && -x "$real" && ! -d "$real" ]] || continue
    shim_is_shim "$real" && continue
    SHIM_PIN_REAL="$real"
    return 0
  done
  return 1
}

# shim_resolve_real <name> <shim_dir> — first PATH entry outside <shim_dir> holding an
# executable <name> that is not itself a shim. Prints it; rc 1 when there is none.
shim_resolve_real() {
  local name="$1" shim_dir="$2" d
  local -a parts
  IFS=':' read -ra parts <<<"$PATH"
  for d in "${parts[@]}"; do
    [[ -n "$d" && "$d" != "$shim_dir" ]] || continue
    [[ -x "$d/$name" && ! -d "$d/$name" ]] || continue
    if shim_is_shim "$d/$name"; then continue; fi
    printf '%s\n' "$d/$name"
    return 0
  done
  return 1
}
