#!/usr/bin/env bash # audit-needed.sh — ¿hay artefactos que piden una librería que NADIE del corpus provee? # # ── POR QUÉ EXISTE ───────────────────────────────────────────────────────────────────────────── # Encontrado el 2026-09-06 corriendo el navegador, no leyendo: `atuq` moría con decenas de # «Error relocating: _ZNKSt5ctypeIcE13_M_widen_initEv: symbol not found». El binario sellado de # firefox declara # # NEEDED libstdc++.so.6 NEEDED libgcc_s.so.1 NEEDED libc.so # # y las dos primeras NO EXISTEN en el store ni en ningún cierre: sólo en `.dev-fs/alpine`, o sea en # el LAB. Es exactamente la lección de `quitar-dep-no-apaga-funcion`: el proyecto la encuentra en el # sysroot del lab y el artefacto sella con un NEEDED que nadie provee. # # LO QUE LO HACE CARO ES QUE NINGUNA MÉTRICA LO VE. `build-state.json` dice `sealed`, # `verificar-repro.sh` dice que REPRODUCE bit a bit, los guardianes de la receta pasan — y el # binario no arranca en la distro. El verde responde «¿construye?», nunca «¿corre?». # # ── QUÉ MIDE, EXACTAMENTE ────────────────────────────────────────────────────────────────────── # Índice de sonames PROVISTOS = todo `*.so*` que haya en el store. Para cada ELF ejecutable del # store, sus `NEEDED` menos lo provisto. Lo que sobra es una dep de ejecución colgante. # # NO mira el lab a propósito: si mirara `.dev-fs/alpine` diría que todo está bien, que es justo la # ilusión que causó el problema. El lab es donde se CONSTRUYE, no donde se corre. # # Uso: scripts/audit-needed.sh [--store DIR] (sale 1 si hay colgantes) set -uo pipefail STORE="${STORE:-./store}" [ "${1:-}" = "--store" ] && STORE="$2" [ -d "$STORE" ] || { echo "no existe $STORE" >&2; exit 2; } tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT # 1. Qué provee el corpus. Sólo el basename: es lo que el loader busca. find "$STORE" -maxdepth 5 -name '*.so' -o -maxdepth 5 -name '*.so.*' 2>/dev/null \ | sed 's|.*/||' | sort -u > "$tmp/provisto" echo "== sonames provistos por el store: $(wc -l < "$tmp/provisto")" # 2. Qué pide cada artefacto. # # ⚠ DOS CORRECCIONES SOBRE LA PRIMERA VERSIÓN, las dos por sub-reportar (2026-09-06): # · Miraba sólo ejecutables y `head -12` por artefacto. El `NEEDED` que faltaba de verdad estaba # en una LIBRERÍA (`libmozsandbox.so` pedía `libnspr4.so`), así que el audit daba «6 recetas» # cuando el artefacto de firefox solo declara 30 sonames. Ahora mira TODOS los ELF. # · No contaba lo que el propio artefacto TRAE. Firefox bundlea su nspr/nss y las encuentra por el # `LD_LIBRARY_PATH` que fija su lanzador; contarlas como colgantes sería un falso positivo. colgantes=0 for d in "$STORE"/*/; do d=${d%/}; nombre=$(basename "$d"); receta=${nombre#*-} # lo que este artefacto se provee a sí mismo find "$d" \( -name '*.so' -o -name '*.so.*' \) 2>/dev/null | sed 's|.*/||' | sort -u > "$tmp/propio" falta="" while IFS= read -r f; do n=$(readelf -d "$f" 2>/dev/null | sed -n 's/.*Shared library: \[\(.*\)\]/\1/p') for so in $n; do grep -qxF "$so" "$tmp/provisto" && continue grep -qxF "$so" "$tmp/propio" && continue case "$so" in libc.so|ld-musl*) continue ;; esac # la libc la pone el sistema base case " $falta " in *" $so "*) ;; *) falta="$falta $so" ;; esac done done < <(find "$d" -type f \( -name '*.so' -o -name '*.so.*' -o -perm -u+x \) 2>/dev/null) [ -n "$falta" ] && { printf ' ✗ %-24s pide y nadie provee:%s\n' "$receta" "$falta"; colgantes=$((colgantes+1)); } done echo "════════════════════════════════════════════════════════════" if [ "$colgantes" -eq 0 ]; then echo " ✅ sin deps de ejecución colgantes" else echo " ⚠ $colgantes artefacto(s) con deps de ejecución COLGANTES" echo " no arrancan fuera del lab, por más que sellen y reproduzcan" fi exit $([ "$colgantes" -eq 0 ] && echo 0 || echo 1)