busybox recortado: 401 -> 277 applets, con vigía y cinco indultos medidos

Paso 1 de docs/plan-botar-busybox.md. 124 applets fuera del defconfig: los 105 que
no se reemplazan (dpkg, rpm, httpd, telnetd, ubi*, nand*, i2c*) y 19 cuya función ya
hace otro componente (init/runit -> arje-zero, syslogd/klogd/logread -> hammerd).
Binario de 1.230.976 a 977.096 bytes.

CORRIGE UN ERROR DEL PLAN: arje-zero y hammerd reemplazan la FUNCIÓN, no el COMANDO
-- son un binario cada uno. Por eso 5 indultos: setuidgid (la card de gitea lo
ejecuta), switch_root (el /init del instalador, antes de que arje exista) y
poweroff/halt/reboot (nada más en el corpus apaga la máquina).

scripts/busybox-vigia.py falla si alguien invoca un applet retirado; lee la lista de
la propia receta para que no haya dos que se desincronicen. Su primera versión daba
91 hits y ninguno real: las cuatro reglas que lo bajaron a 0 están documentadas.

El mapa applet->símbolo sale de las líneas //applet: del fuente, no de poner el
nombre en mayúsculas: eso falla en 6 casos medidos, dos de ellos en la lista.

Verificado: 277 applets exactos, los 5 indultados presentes, y zlib-ng, jq y
logrotate reconstruidas y selladas con el busybox recortado -- el sandbox sigue en
pie. Son 3 de las 24 dependientes, no las 24, y el boot de la imagen no se probó.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sergio
2026-09-21 18:26:01 +00:00
co-authored by Claude Opus 5
parent 58e7972977
commit c5a27d9b4c
4 changed files with 651 additions and 408 deletions
+92 -5
View File
@@ -69,8 +69,11 @@ migración de llamadores, no un borrado.** Mirar quién los invoca en `scripts/`
| `diffutils` | 2 | `diff cmp` |
| `findutils` / `findutils-xargs` / `libarchive` | 3 | `find` · `xargs` · `cpio` |
**Los 24 de `arje-zero` y `hammerd` no cuestan nada**: el reemplazo ya corre en el producto. Son
applets instalados que nadie ejecuta. Salen del `defconfig` y se prueba que la imagen arranque igual.
⚠ **CORRECCIÓN (2026-09-21, al ejecutar el paso 1): los 24 de `arje-zero` y `hammerd` NO son
gratis, y esta tabla decía mal.** Esas dos recetas reemplazan la **función**, no el **comando**:
cada una instala UN binario (`-p arje-zero`, `-p hammerd`), así que no existe un `poweroff`, un
`logger` ni un `setuidgid` que las respalde. «Cubierto» significaba «nadie necesita correr esto»,
y para 5 de los 24 era falso. Ver los indultos del paso 1.
### 128 · Sin dueño
@@ -135,9 +138,7 @@ trabajo #1 es medirlo, no adoptarlo.**
Cada paso es una unidad de trabajo con su criterio de terminado. Los tres primeros **no tocan el
frente C** y se pueden hacer hoy.
1. **Recortar el `defconfig`.** Sacar los 105 de `BORRAR` + los 24 que `arje-zero`/`hammerd` ya
reemplazan = **129 applets menos, cero código escrito**. Terminado cuando la imagen arranca, el
E2E de QEMU pasa y `find . -type l | wc -l` baja de 401 a ~272.
1. ~~**Recortar el `defconfig`.**~~ **HECHO, 2026-09-21 — 401 → 277 applets. Veredicto al final.**
2. **Declarar la munición que ya existe.** `uutils`, `findutils`, `findutils-xargs`, `diffutils`,
`brush`, `gzip`, `libarchive` a los perfiles de `targets.toml`. Terminado cuando `build-state.py`
los marca con perfil y la imagen los lleva.
@@ -330,3 +331,89 @@ y valen 129 applets y dos piezas de userland. Gastar el turno en pelear el shell
Lo que sí conviene hacer ya, porque es gratis: **suscribirse a #1394** y **medir brush como `/bin/sh`
del PRODUCTO** (`product-boot-test.sh` + las cards), que es un consumidor que no genera libtool y
donde brush no tiene ninguna evidencia en contra.
---
# Paso 1 — el recorte, HECHO y MEDIDO
**Fecha:** 2026-09-21 · **Dónde:** `dev.gioser.net`, bajo `flock -o`, contra un store tirable y con
un directorio de recetas por hardlinks — el `recipes/` del worker no se tocó.
> **401 → 277 applets.** 124 retirados, 5 indultados, binario de **1.230.976 → 977.096 bytes**
> (20,6 %). Nuevo hash `b3:a24fdbf6…` (el vigente era `2a2b1280…`). Y **3 de las 24 recetas que
> declaran busybox build-dep se reconstruyeron y sellaron con el recortado**: el sandbox sigue en pie.
## Lo que el ejercicio corrigió del plan
**Mi tabla decía que los 24 applets de `arje-zero`/`hammerd` eran gratis. Es falso para 5 de ellos.**
Esas recetas instalan **un binario cada una** (`-p arje-zero`, `-p hammerd`): reemplazan la FUNCIÓN,
no el COMANDO. No hay un `poweroff` ni un `setuidgid` detrás. Los cinco indultos, con su razón:
| indultado | por qué se queda |
|---|---|
| `setuidgid` | la card de **gitea** hace `setuidgid gitea` (`recipes/gitea.toml:89`, «applet de busybox, comprobado presente»). Sin él el servicio arranca, muere y reintenta para siempre |
| `switch_root` | el `/init` del initramfs del instalador lo ejecuta (`scripts/takana-live-install.sh:375`), **antes** de que arje exista |
| `poweroff` · `halt` · `reboot` | **nada más en el corpus apaga la máquina.** arje-zero es PID 1, no una CLI. Quitarlos deja un producto que no se puede apagar desde la shell |
Los tres primeros los encontró el vigía; los tres últimos, preguntarse qué instala arje-zero de
verdad en vez de creerle a mi propia tabla.
## El vigía — y por qué un vigía ingenuo no sirve
`scripts/busybox-vigia.py` falla si alguna receta, script o card invoca un applet retirado. **Lee la
lista de la propia receta** (`RETIRADOS='…'`) cruzada con la columna `simbolo_kconfig` del TSV, así
que no hay dos listas que se desincronicen.
Su primera versión daba **91 hits y ninguno real**. Las cuatro reglas que lo bajaron a 0, todas
medidas contra falsos positivos concretos:
| regla | qué mataba |
|---|---|
| quitar comentarios antes de buscar | `# busybox provee sendmail, no mail` (logrotate), `/// los dos fsync no son prolijidad` |
| en Rust/Python, mirar sólo cadenas **que parezcan una línea de comandos** | `"init"` como nombre de capacidad, `tree` como variable, `rx = re.compile(…)` |
| ignorar nombres que el propio script define como función | `$(ts)` en los seis scripts de `scripts/farm/` |
| ignorar patrones de `case` y `command -v X` | `proc\|sys\|dev\|init) continue ;;` · `command -v dpkg` en el censo de la mudanza |
Y `[` / `[[` no se vigilan: son palabras del shell, y todo script bash del hub los usa sin que eso
sea una llamada a un applet.
**El vigía mira ESTE repo.** No ve los `configure` de terceros que corren dentro del sandbox, que
son quienes podrían llamar a `which` o `unzip`. Por eso la verificación de abajo no es opcional.
## El mapa applet → símbolo NO se deriva por mayúsculas
Sale de las líneas `//applet:` del fuente de busybox 1.36.1 y vive en la columna `simbolo_kconfig`
del TSV. Poner el nombre en mayúsculas **falla en 6 casos medidos**: `[``TEST1`, `[[``TEST2`,
`arch``BB_ARCH`, `nbd-client``NBDCLIENT`, `sh``SH_IS_ASH`, `sysctl``BB_SYSCTL`. Dos de esos seis
estaban en la lista a retirar.
## Verificación
| qué | resultado |
|---|---|
| applets tras el recorte | **277** (401 124), exactamente los previstos |
| los 5 indultados presentes | ✅ y también `sh` y `getty` |
| los que debían irse | ✅ `httpd telnetd dpkg rpm init syslogd runsv tree which unzip` fuera |
| **el sandbox sigue construyendo** | ✅ `zlib-ng`, `jq` y `logrotate` reconstruidas y selladas con el busybox recortado |
**Son 3 de las 24, no las 24.** Las otras 21 —entre ellas `linux`, `llama-cpp`, `protobuf`,
`qdrant`— no se probaron: son horas de cómputo en una caja que está con la campaña de KDE. Si alguna
usa un applet retirado, se verá en su próximo build y el vigía **no** lo habría anticipado, porque el
llamador estaría en un `configure` de terceros.
## El coste, dicho antes de que sorprenda
**Cambiar esta lista re-hashea busybox, y con él las 24 recetas que lo declaran build-dep**:
`abseil-cpp chrony cronie crun dwarves firmware-tigerlake ia-modelo-embeddings intel-ucode jq libmnl
libnftnl linux linux-firmware llama-cpp logrotate nftables opensmtpd popt protobuf qdrant
sof-firmware valkey wireless-regdb zlib-ng`. **`linux` está en la lista**: el kernel se reconstruye.
La granja lo hará en su propio ciclo; no se disparó a mano.
## Lo que queda del paso 1
- **Arrancar una imagen.** Lo medido es que el artefacto sella y que el sandbox construye. Que el
producto **bootee** con 277 applets no se probó: falta `product-boot-test.sh`.
- **Las otras 21 dependientes**, cuando la granja llegue.
- El siguiente corte natural son los `BORRAR` que no se tocaron por prudencia y los 272 que siguen:
el número baja a 0 por el paso 2 (declarar la munición sellada) y el 5 (`grep`/`sed` POSIX).