Files
takana/scripts/audit-needed.sh
T
Sergio 2be672415c audit-needed: cazar deps de ejecución colgantes — firefox no arranca en la distro
Encontrado corriendo el navegador, no leyendo. Lancé `atuq` bajo el sway
headless y murió con decenas de «Error relocating: _ZNKSt5ctypeIcE...: 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 hidratado: sólo en
.dev-fs/alpine, o sea en el LAB. Es exactamente la lección que la memoria
`quitar-dep-no-apaga-funcion` describe: 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 tres guardianes de
la receta pasan (RLBox en el binario, MOZ_REQUIRE_SIGNING, BuildID determinista)
— y el binario no arranca fuera del lab. El verde responde «¿construye?», nunca
«¿corre?».

El audit recorre el store, indexa qué sonames PROVEE el corpus y resta los NEEDED
de cada ELF. No mira el lab a propósito: mirarlo diría que todo está bien, que es
justo la ilusión que causó el problema.

Resultado de hoy: 25 artefactos colgantes, 6 recetas distintas —atuq, firefox,
waterfox, librsvg, spidermonkey, mesa-llvmpipe— y sólo DOS sonames faltan en todo
el corpus. O sea que falta UNA receta que provea las libs de runtime de gcc.

Gravedad: firefox y atuq son raíces de los CUATRO perfiles de escritorio
(cosmic, gnome, kde, sway), así que hoy las cuatro imágenes llevan un navegador
que no puede arrancar. La receta de firefox razona sobre esto en su comentario
—«clang++ de Alpine usa la libstdc++ COMPARTIDA ⇒ el NEEDED existe»— pero cierra
el requisito de BUILD y deja abierto el de EJECUCIÓN. hammer tiene
`[deps] runtime` y ninguna de las seis lo declara.
2026-09-06 19:23:02 +00:00

64 lines
3.4 KiB
Bash
Executable File

#!/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.
colgantes=0
for d in "$STORE"/*/; do
d=${d%/}; nombre=$(basename "$d"); receta=${nombre#*-}
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
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" -maxdepth 4 -type f -perm -u+x 2>/dev/null | head -12)
[ -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)