brush: la causa era el desescapado de barras dentro de comillas invertidas — reportada, y adelantar el pin no sirve

Bajando al config.status que genera el libtool apareció el mecanismo, y es de una
línea: brush aplica a `...` las reglas de $( ). POSIX manda que dentro de comillas
invertidas la barra se ELIMINE cuando precede a $, ` o \ (2.6.3). Que el caso con
$( ) sí esté bien es lo que delata la causa.

Por eso rompe libtool: el case que decide si cada variable necesita escapado evalúa
a cadena vacía bajo brush, así que todas caen en la rama sin escapar. El configure
termina bien; el fichero sale mal escrito.

Se construyó main de hoy (737dd57e, cuatro meses y medio sobre nuestro pin) y
reproduce idéntico los cuatro casos ⇒ adelantar el pin no arregla nada.

Reportado: https://github.com/reubeno/brush/issues/1394

Y la recomendación: no pelear el shell. Los pasos 1, 2 y 5 del plan no dependen de
él y valen 129 applets y dos piezas de userland.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sergio
2026-09-21 17:58:30 +00:00
co-authored by Claude Opus 5
parent 0a82d3f701
commit 27d2dfd770
+65 -8
View File
@@ -249,11 +249,43 @@ Ese fichero ya no lo parsea **nadie** — ni brush ni busybox lo aceptan (`rc=2`
368). Y el contraprueba cierra el caso: **el `libtool` del control lo parsea brush sin quejarse
(`rc=0`)**. ⇒ El parser de brush está bien; lo que difiere es lo que su shell PRODUCE.
**El mecanismo exacto no está determinado, y conviene decirlo así en vez de inventarlo.** Las tres
sospechas obvias se probaron aisladas y **las tres dan idéntico** en ambos shells: `echo` con barras
invertidas, sustitución de comando preservando `\\`, y el propio `func_quote_for_eval` de libtool
(`sed_quote_subst`) aplicado a mano. Lo medido es el diff de 25 líneas y que el `configure` termina
bien; el resto es hipótesis.
### El mecanismo, encontrado (2026-09-21, después de escribir lo de arriba)
Primero se escribió acá que la causa no estaba determinada, tras descartar las tres sospechas
obvias —`echo` con barras invertidas, sustitución de comando preservando `\\`, y el
`func_quote_for_eval` de libtool aplicado a mano—, que dan **idéntico** en ambos shells. La causa
apareció bajando al `config.status` que genera el `libtool`. **Es de una línea:**
```sh
V=hola
echo "a: [`echo \\$V`]" # POSIX: $V · brush: \hola
echo "b: [`echo \\\\`]" # POSIX: \ · brush: \\
echo "c: [`echo \$V`]" # POSIX: hola · brush: $V
echo "d: [$(echo \\$V)]" # POSIX: \hola · brush: \hola ✅
```
**brush aplica a las comillas invertidas las reglas de `$( )`.** POSIX manda otra cosa: dentro de
`` `` `` la barra invertida conserva su significado literal **salvo** cuando precede a `$`,
`` ` `` o `\`, donde se ELIMINA antes de parsear el texto del comando (POSIX.1-2024, 2.6.3). Que el
caso **d** sí esté bien es lo que delata la causa.
Por qué rompe libtool: el `config.status` generado trae este bucle, que decide si cada variable
necesita escapado antes de escribirla en el `libtool`:
```sh
for var in SHELL ECHO PATH_SEPARATOR SED GREP …; do
case `eval \\$ECHO \\""\\$$var"\\"` in
*[\\\`\"\$]*) eval "lt_$var=\\\"\`\$ECHO \"\$$var\" | \$SED \"\$sed_quote_subst\"\`\\\"" ;;
*) eval "lt_$var=\\\"\$$var\\\"" ;;
esac
done
```
Bajo brush ese `case` evalúa a **cadena vacía**, así que **todas** las variables caen en la rama sin
escapar. De ahí las 25 líneas y de ahí que el `configure` termine bien: no falla nada, sólo sale mal
escrito.
**Reportado upstream: [reubeno/brush#1394](https://github.com/reubeno/brush/issues/1394).**
## Y el otro dato, que no es un fallo pero decide
@@ -267,9 +299,34 @@ vuelve medible más lento. Es un argumento independiente del bug, y no se arregl
2. **El frente del producto sigue abierto**: las cards, la getty y la shell interactiva no generan
libtool. Ahí brush no tiene ninguna evidencia en contra — pero tampoco se probó, y el
`product-boot-test.sh` es la prueba que falta.
3. **Hay un bug upstream que reportar** (reubeno/brush): `configure` de autotools genera un `libtool`
con un nivel de escapado de menos. El reproductor es una receta autotools cualquiera, y el
artefacto es el diff de 25 líneas de arriba.
3. **Reportado upstream: [reubeno/brush#1394](https://github.com/reubeno/brush/issues/1394)**, con
el reproductor de una línea, la tabla de los cuatro casos y el fragmento de `config.status`.
4. **El paso 5 del plan (grep/sed POSIX en Rust) sube de prioridad**, porque no depende del shell.
5. **No se tocó el lab compartido**: copia con hardlinks y stores tirables. El worker siguió
compilando su cola en paralelo, serializado por el `flock`.
## ¿Parchar, o buscar otra opción?
**Primero lo que cierra la pregunta barata: adelantar el pin NO sirve.** Nuestra receta apunta a
`96a26d0c` (tag `brush-shell-v0.4.0`, mayo 2026), que sigue siendo el último release. Se construyó
**`main` de hoy** (`737dd57e`, 2026-09-21 — cuatro meses y medio de trabajo encima) y **reproduce
idéntico**, los cuatro casos. No hay nada que esperar de la rama.
Lo que queda, en orden de coste:
| opción | qué cuesta | qué deja |
|---|---|---|
| **Esperar a #1394** | cero | el upstream es activísimo (issue #1393 de anteayer) y el bug es acotado, pero no es nuestro calendario |
| **Parchar brush en la receta** | un `.patch` en el árbol, como ya hacen 13 recetas | ⚠ es un parche **al parser de un shell**, que es el componente más cargado de todos. Y el bug apareció en el PRIMER build real: no es prueba de que sea el único |
| **dash** | receta nueva, C, ~200 KB. Es el `sh` de referencia POSIX | resuelve el sandbox YA — y no avanza nada la soberanía en Rust |
| **bash como `/bin/sh` del producto** | cero: ya está sellada Y en los 7 perfiles | tapa el frente B sin escribir nada. No sirve para el sandbox sin medirlo antes |
| **Dejar busybox donde está** | cero | es lo que dice el plan: el shell NO es por donde se empieza |
**La recomendación es la última, y no por conservadurismo.** El plan ya ordenaba los pasos 1, 2 y 5
—recortar el defconfig, declarar lo sellado, escribir `grep`/`sed` POSIX— **sin depender del shell**,
y valen 129 applets y dos piezas de userland. Gastar el turno en pelear el shell es empezar por el
único frente que está bloqueado por terceros.
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.