#!/usr/bin/env bash # nc-find.sh — cross-site Cloverleaf thread/protocol search. Native v3. # # Replaces (in v3 terms) the v1 family `tbn`, `tbp`, `tbh`, `tbpr`, plus the # v1 ` where` command. Searches every NetConfig under $HCIROOT (or a # passed list) without invoking v1/v2 wrappers, and emits site, thread, port, # host, process, NetConfig path, and line number for each match. # # Usage: # nc-find.sh --name PATTERN # like tbn: case-insensitive substring on thread name # nc-find.sh --port PORT # like tbp: exact port match # nc-find.sh --host HOST # like tbh: substring on host # nc-find.sh --process PROC # like tbpr: substring on PROCESSNAME # nc-find.sh --where THREAD # like ` where`: file:line of the thread's declaration # nc-find.sh --xlate XLATENAME # threads referencing a specific .xlt file # nc-find.sh --tclproc TCLPROC # threads referencing a specific tclproc # # Common flags: # --hciroot DIR # default $HCIROOT # --netconfigs PATHS # colon-separated explicit list (overrides --hciroot) # --format tsv|table|jsonl # default: table # --case-sensitive # default: case-insensitive for name/host/process set -o pipefail NC_SELF="$0" LIB_DIR="$(cd "$(dirname "$NC_SELF")" && pwd)" NCP="$LIB_DIR/nc-parse.sh" die() { printf 'nc-find: %s\n' "$*" >&2; exit 1; } MODE="" QUERY="" HCIROOT_OVERRIDE="" NETCONFIGS_OVERRIDE="" FORMAT="table" CASE_SENSITIVE=0 while [ $# -gt 0 ]; do case "$1" in --name) shift; MODE="name"; QUERY="$1" ;; --port) shift; MODE="port"; QUERY="$1" ;; --host) shift; MODE="host"; QUERY="$1" ;; --process) shift; MODE="process"; QUERY="$1" ;; --where) shift; MODE="where"; QUERY="$1" ;; --xlate) shift; MODE="xlate"; QUERY="$1" ;; --tclproc) shift; MODE="tclproc"; QUERY="$1" ;; --hciroot) shift; HCIROOT_OVERRIDE="$1" ;; --netconfigs) shift; NETCONFIGS_OVERRIDE="$1" ;; --format) shift; FORMAT="$1" ;; --case-sensitive) CASE_SENSITIVE=1 ;; -h|--help) sed -n '2,22p' "$NC_SELF"; exit 0 ;; -*) die "unknown flag: $1" ;; *) die "extra arg: $1" ;; esac shift done [ -n "$MODE" ] || die "specify a search mode: --name | --port | --host | --process | --where | --xlate | --tclproc" case "$FORMAT" in tsv|table|jsonl) ;; *) die "bad --format: $FORMAT" ;; esac # Resolve NetConfig list NCONFIGS=() if [ -n "$NETCONFIGS_OVERRIDE" ]; then IFS=':' read -ra NCONFIGS <<< "$NETCONFIGS_OVERRIDE" else ROOT="${HCIROOT_OVERRIDE:-${HCIROOT:-}}" [ -n "$ROOT" ] || die "no \$HCIROOT and no --hciroot/--netconfigs" while IFS= read -r nc; do NCONFIGS+=("$nc"); done < <(find "$ROOT" -maxdepth 2 -name NetConfig -type f 2>/dev/null) fi [ ${#NCONFIGS[@]} -gt 0 ] || die "no NetConfig files found" # Use a temp file to collect results, then format RESULTS=$(mktemp) trap 'rm -f "$RESULTS"' EXIT # Helper: emit one result row to $RESULTS emit() { # cols: site \t thread \t port \t host \t process \t direction \t file \t line printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n" "$@" >> "$RESULTS" } GREP_FLAGS="" [ "$CASE_SENSITIVE" = "0" ] && GREP_FLAGS="-i" # Per-NetConfig scanning for nc in "${NCONFIGS[@]}"; do site=$(basename "$(dirname "$nc")") case "$MODE" in name|where) # Find protocol declarations matching the pattern if [ "$MODE" = "where" ]; then # Exact match (one specific thread name) line=$(grep -nE "^protocol[[:space:]]+${QUERY}[[:space:]]+\{" "$nc" 2>/dev/null | head -1 | cut -d: -f1) if [ -n "$line" ]; then pname=$("$NCP" protocol-field "$nc" "$QUERY" PROCESSNAME 2>/dev/null | head -1) pport=$("$NCP" protocol-nested "$nc" "$QUERY" PROTOCOL.PORT 2>/dev/null | head -1 | sed 's/^{}$//') phost=$("$NCP" protocol-nested "$nc" "$QUERY" PROTOCOL.HOST 2>/dev/null | head -1 | sed 's/^{}$//') isserver=$("$NCP" protocol-nested "$nc" "$QUERY" PROTOCOL.ISSERVER 2>/dev/null | head -1) obib=$("$NCP" protocol-field "$nc" "$QUERY" OBWORKASIB 2>/dev/null | head -1) outonly=$("$NCP" protocol-field "$nc" "$QUERY" OUTBOUNDONLY 2>/dev/null | head -1) direction="?" [ "$isserver" = "1" ] && direction="inbound-tcp" [ "$obib" = "1" ] && [ "$direction" = "?" ] && direction="inbound-icl" [ "$outonly" = "1" ] && [ "$direction" = "?" ] && direction="outbound" emit "$site" "$QUERY" "${pport:-—}" "${phost:-—}" "${pname:-?}" "$direction" "$nc" "$line" fi else # Partial match (substring) while IFS= read -r raw; do line=$(printf '%s' "$raw" | cut -d: -f1) thread_name=$(printf '%s' "$raw" | sed -n 's/^[0-9]*:protocol[[:space:]]\+\([A-Za-z0-9_]\+\)[[:space:]]*{.*$/\1/p') [ -z "$thread_name" ] && continue pname=$("$NCP" protocol-field "$nc" "$thread_name" PROCESSNAME 2>/dev/null | head -1) pport=$("$NCP" protocol-nested "$nc" "$thread_name" PROTOCOL.PORT 2>/dev/null | head -1 | sed 's/^{}$//') phost=$("$NCP" protocol-nested "$nc" "$thread_name" PROTOCOL.HOST 2>/dev/null | head -1 | sed 's/^{}$//') isserver=$("$NCP" protocol-nested "$nc" "$thread_name" PROTOCOL.ISSERVER 2>/dev/null | head -1) obib=$("$NCP" protocol-field "$nc" "$thread_name" OBWORKASIB 2>/dev/null | head -1) outonly=$("$NCP" protocol-field "$nc" "$thread_name" OUTBOUNDONLY 2>/dev/null | head -1) direction="?" [ "$isserver" = "1" ] && direction="inbound-tcp" [ "$obib" = "1" ] && [ "$direction" = "?" ] && direction="inbound-icl" [ "$outonly" = "1" ] && [ "$direction" = "?" ] && direction="outbound" emit "$site" "$thread_name" "${pport:-—}" "${phost:-—}" "${pname:-?}" "$direction" "$nc" "$line" done < <(grep -nE $GREP_FLAGS "^protocol[[:space:]]+[A-Za-z0-9_]*${QUERY}[A-Za-z0-9_]*[[:space:]]*\{" "$nc" 2>/dev/null) fi ;; port) # Find protocols whose inner PROTOCOL.PORT equals QUERY. "$NCP" list-protocols "$nc" 2>/dev/null | while IFS= read -r tname; do p=$("$NCP" protocol-nested "$nc" "$tname" PROTOCOL.PORT 2>/dev/null | head -1) if [ "$p" = "$QUERY" ]; then pname=$("$NCP" protocol-field "$nc" "$tname" PROCESSNAME 2>/dev/null | head -1) phost=$("$NCP" protocol-nested "$nc" "$tname" PROTOCOL.HOST 2>/dev/null | head -1 | sed 's/^{}$//') line=$("$NCP" protocol-line "$nc" "$tname" 2>/dev/null) isserver=$("$NCP" protocol-nested "$nc" "$tname" PROTOCOL.ISSERVER 2>/dev/null | head -1) obib=$("$NCP" protocol-field "$nc" "$tname" OBWORKASIB 2>/dev/null | head -1) outonly=$("$NCP" protocol-field "$nc" "$tname" OUTBOUNDONLY 2>/dev/null | head -1) direction="?" [ "$isserver" = "1" ] && direction="inbound-tcp" [ "$obib" = "1" ] && [ "$direction" = "?" ] && direction="inbound-icl" [ "$outonly" = "1" ] && [ "$direction" = "?" ] && direction="outbound" emit "$site" "$tname" "$p" "${phost:-—}" "${pname:-?}" "$direction" "$nc" "${line:-?}" fi done ;; host) "$NCP" list-protocols "$nc" 2>/dev/null | while IFS= read -r tname; do h=$("$NCP" protocol-nested "$nc" "$tname" PROTOCOL.HOST 2>/dev/null | head -1 | sed 's/^{}$//') if [ "$CASE_SENSITIVE" = "1" ]; then [[ "$h" == *"$QUERY"* ]] || continue else shopt -s nocasematch 2>/dev/null [[ "$h" == *"$QUERY"* ]] || { shopt -u nocasematch 2>/dev/null; continue; } shopt -u nocasematch 2>/dev/null fi [ -z "$h" ] && continue pname=$("$NCP" protocol-field "$nc" "$tname" PROCESSNAME 2>/dev/null | head -1) p=$("$NCP" protocol-nested "$nc" "$tname" PROTOCOL.PORT 2>/dev/null | head -1 | sed 's/^{}$//') line=$("$NCP" protocol-line "$nc" "$tname" 2>/dev/null) emit "$site" "$tname" "${p:-—}" "$h" "${pname:-?}" "?" "$nc" "${line:-?}" done ;; process) "$NCP" list-protocols "$nc" 2>/dev/null | while IFS= read -r tname; do pname=$("$NCP" protocol-field "$nc" "$tname" PROCESSNAME 2>/dev/null | head -1) if [ "$CASE_SENSITIVE" = "1" ]; then [[ "$pname" == *"$QUERY"* ]] || continue else shopt -s nocasematch 2>/dev/null [[ "$pname" == *"$QUERY"* ]] || { shopt -u nocasematch 2>/dev/null; continue; } shopt -u nocasematch 2>/dev/null fi p=$("$NCP" protocol-nested "$nc" "$tname" PROTOCOL.PORT 2>/dev/null | head -1 | sed 's/^{}$//') h=$("$NCP" protocol-nested "$nc" "$tname" PROTOCOL.HOST 2>/dev/null | head -1 | sed 's/^{}$//') line=$("$NCP" protocol-line "$nc" "$tname" 2>/dev/null) emit "$site" "$tname" "${p:-—}" "${h:-—}" "$pname" "?" "$nc" "${line:-?}" done ;; xlate|tclproc) # threads that reference a given xlate or tclproc need_pattern="$QUERY" "$NCP" list-protocols "$nc" 2>/dev/null | while IFS= read -r tname; do if [ "$MODE" = "xlate" ]; then hits=$("$NCP" xlate-refs "$nc" "$tname" 2>/dev/null | grep -F -- "$need_pattern" || true) else hits=$("$NCP" tclproc-refs "$nc" "$tname" 2>/dev/null | grep -F -- "$need_pattern" || true) fi [ -z "$hits" ] && continue pname=$("$NCP" protocol-field "$nc" "$tname" PROCESSNAME 2>/dev/null | head -1) line=$("$NCP" protocol-line "$nc" "$tname" 2>/dev/null) emit "$site" "$tname" "—" "—" "${pname:-?}" "?" "$nc" "${line:-?}" done ;; esac done # Format output case "$FORMAT" in tsv) printf "site\tthread\tport\thost\tprocess\tdirection\tfile\tline\n" cat "$RESULTS" ;; table) { printf "site\tthread\tport\thost\tprocess\tdirection\tline\n" awk -F'\t' '{printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", $1, $2, $3, $4, $5, $6, $8}' "$RESULTS" } | awk -F'\t' ' { for (i=1;i<=NF;i++){ if (length($i)>w[i]) w[i]=length($i); cell[NR,i]=$i }; rows=NR; cols=NF } END { for (r=1; r<=rows; r++) { for (c=1; c<=cols; c++) printf "%-*s ", w[c], cell[r,c] printf "\n" if (r==1) { for (c=1; c<=cols; c++) for (k=0;k&2