From 6886b1541cb45e9498f0135ba17004635944dd54 Mon Sep 17 00:00:00 2001 From: Sergio Date: Mon, 7 Sep 2026 00:51:56 +0000 Subject: [PATCH] =?UTF-8?q?libnotify:=20el=20primer=20hueco=20que=20el=20m?= =?UTF-8?q?apa=20del=20=C2=A76.10=20encontr=C3=B3=20Y=20cerr=C3=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `libxul.so` lleva la cadena `libnotify.so.4` adentro y la abre por dlopen cuando una página pide permiso para notificar. Nadie en el corpus la proveía, así que la función quedaba apagada SIN UN SOLO MENSAJE: el navegador arranca, la web pide notificaciones, y no pasa nada. No lo veía ninguna herramienta porque no hay NEEDED en ningún ELF — es el tercer escalón, y sobrevivió a vigia-sonames con los cinco perfiles en CERO. Receta nueva, 0.8.8, SÓLO en variante compartida y eso no es un olvido: a un `dlopen("libnotify.so.4")` una `.a` no le sirve de nada, así que una receta estática sería un artefacto que nadie puede consumir. Dos cosas que la receta se comió y quedan escritas: 1. `libpng-shared` hace falta porque las deps de hammer NO son transitivas: el `.pc` de gdk-pixbuf-2.0 declara `Requires: libpng` y el meson muere con un mensaje que nombra a gdk-pixbuf —que sí está— en vez de a lo que falta. 2. El guardián informaba «18 bytes» y parecía una librería vacía: `stat -c%s` no sigue el symlink, y `libnotify.so.4` apunta a `libnotify.so.4.0.0`. El artefacto estaba bien y el MENSAJE mentía. Con `-L` son 162.928 bytes. Se arregla el mensaje porque es lo que alguien va a leer a las tres de la mañana, y de paso el guardián exige el fichero real, no sólo el nombre. ⚠ Y lo que NO arregla, escrito en la receta para que nadie lea de más: libnotify no trae daemon, manda org.freedesktop.Notifications por D-Bus. Tenerla resuelve la mitad —que firefox la encuentre—; la otra mitad es que en la imagen haya alguien escuchando ese nombre. Verificado con el propio mapa: `--dlopen` pasa de 25 huecos a 24 y libnotify.so.4 desaparece; el soname viejo `.so.1` se reclasifica de «hueco» a «ruido», que es lo que ahora es. El guardián normal sigue en CERO con un soname más pedido (66). Queda pendiente la membresía de perfil en `docs/state/targets.toml`, que es catálogo compartido y no lo toco sin decidirlo. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01UgNtJEFetMYbXax5dVUjZZ --- docs/26-atuq-envoltorio-gecko.md | 8 ++- recipes/libnotify.toml | 92 ++++++++++++++++++++++++++++++++ scripts/atuq-nested.sh | 1 + scripts/test-atuq-rootfs.py | 3 +- 4 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 recipes/libnotify.toml diff --git a/docs/26-atuq-envoltorio-gecko.md b/docs/26-atuq-envoltorio-gecko.md index 9f72516a..df9b1d6d 100644 --- a/docs/26-atuq-envoltorio-gecko.md +++ b/docs/26-atuq-envoltorio-gecko.md @@ -474,7 +474,7 @@ De 85 cadenas con forma de soname, **47 no tienen quién las provea**. Triadas: | Qué se pierde | Estado | |---|---| | **Códecs del sistema (H.264/AAC)** | hueco — no hay receta de `ffmpeg`. Lo bundleado cubre VP9/AV1/Opus, no H.264 | -| **Notificaciones web** (`libnotify`) | hueco — sin receta | +| **Notificaciones web** (`libnotify`) | ✅ **CERRADO el 2026-09-07**: `recipes/libnotify.toml`, 0.8.8, compartida. Es el primer hueco que este mapa encontró Y cerró | | **Llavero** (`libsecret`) | hueco — pero **la receta ya existe** en `incoming-gnome` | | **Sonidos del sistema** (`libcanberra`) | hueco — receta en `incoming-kde` | | **WebGPU / vulkan** (`libvulkan`) | hueco — receta `vulkan-loader` en `incoming-kde` | @@ -485,6 +485,12 @@ De 85 cadenas con forma de soname, **47 no tienen quién las provea**. Triadas: | Kerberos, menú global, ML | fuera de alcance | | `libdl/libpthread/librt/libudev.so.0/libfreebl3` | ruido: musl los funde, o el soname es viejo | +**El primero ya está cerrado.** `libnotify` no tenía receta y ahora la tiene: es la prueba de que el +mapa sirve para algo más que mirarlo. ⚠ Con una advertencia que va escrita en la propia receta: +`libnotify` no trae un daemon, manda `org.freedesktop.Notifications` por D-Bus ⇒ tenerla resuelve la +mitad —que firefox la encuentre— y la otra mitad es que en la imagen haya alguien escuchando ese +nombre. Nadie debería leer «notificaciones arregladas» y esperar un globo en un QEMU pelado. + **Tres de los siete huecos son baratos** —`libsecret`, `libcanberra` y `vulkan-loader` ya tienen receta y están en colas `incoming`—, así que son promoción y no autoría. Los otros cuatro piden receta nueva, y el más caro para el usuario es `ffmpeg`: sin él, un sitio que sirva H.264 no diff --git a/recipes/libnotify.toml b/recipes/libnotify.toml new file mode 100644 index 00000000..f3b666df --- /dev/null +++ b/recipes/libnotify.toml @@ -0,0 +1,92 @@ +# libnotify 0.8.8 — las NOTIFICACIONES DE ESCRITORIO, que hoy `atuq` no puede mostrar. +# +# ══ POR QUÉ ENTRA AHORA, Y CÓMO SE SUPO QUE FALTABA ════════════════════════════════════════════ +# No salió de una lista de deseos: salió de `scripts/test-atuq-rootfs.py --dlopen` (SDD 26 §6.10). +# `libxul.so` lleva la cadena `libnotify.so.4` adentro y la abre con `dlopen` cuando una página pide +# permiso para notificar. **Nadie en el corpus la proveía**, así que la función quedaba apagada sin +# un solo mensaje de error: el navegador arranca, la web pide notificaciones, y no pasa nada. +# +# Es el tercer escalón de la jerarquía de lo que se puede auditar: un `NEEDED` lo ve cualquier +# herramienta de `readelf`; una cadena literal dentro de un `dlopen` no la ve NINGUNA. Por eso este +# hueco sobrevivió a `vigia-sonames.py` estando los cinco perfiles en CERO. +# +# ══ SÓLO EXISTE LA VARIANTE COMPARTIDA, Y NO ES UN OLVIDO ══════════════════════════════════════ +# El corpus construye estático por defecto y agrega variantes `-shared` cuando hace falta. Acá la +# ÚNICA forma útil es la compartida: a un `dlopen("libnotify.so.4")` una `.a` no le sirve para nada. +# Una receta estática de esto sería un artefacto que nadie puede consumir, así que no se escribe —y +# por eso esta receta se llama `libnotify` a secas y va con `link = "dynamic"`. +# +# ══ LO QUE SE APAGA, Y POR QUÉ ═════════════════════════════════════════════════════════════════ +# -Dtests=false no entran al artefacto. +# -Ddocbook_docs=disabled exige docbook-xsl y xsltproc; documentación que la distro no distribuye. +# -Dgtk_doc=false idem gi-docgen. +# -Dman=false pide xsltproc por el mismo camino. +# -Dintrospection=disabled la introspección es para lenguajes dinámicos que la consumen (gjs, +# python-gi); firefox la abre por `dlopen` en C y no la usa. Encenderla +# arrastraría gobject-introspection entero, que es una dep grande y un +# `.typelib` que nadie va a leer. Si algún día GNOME lo pide, se enciende +# ahí y no acá — es la misma regla que ya siguen gdk-pixbuf y gtk3. +# +# ⚠ `libnotify` NO habla con un daemon propio: manda `org.freedesktop.Notifications` por D-Bus. O sea +# que tenerla resuelve la MITAD del problema —que firefox la encuentre— y la otra mitad es que en la +# imagen haya alguien escuchando ese nombre (el shell de GNOME, `mako` en sway, el de Plasma). Se +# escribe acá para que nadie lea «notificaciones arregladas» y espere ver un globo en un QEMU pelado. +name = "libnotify" +version = "0.8.8" +license = "LGPL-2.1-or-later" + +[source] +tarball = "https://download.gnome.org/sources/libnotify/0.8/libnotify-0.8.8.tar.xz" +sha256 = "23420ef619dc2cb5aebad613f4823a2fa41c07e5a1d05628d40f6ec4b35bfddd" + +[build] +compiler = "zig-cc" +target = "x86_64-linux-musl" +link = "dynamic" + +[build.phases] +configure = "PKG_CONFIG_PATH=/usr/lib/pkgconfig PYTHONPATH=/usr/lib/python3.12/site-packages meson setup output --prefix=/usr --buildtype=release --wrap-mode=nodownload -Ddefault_library=shared -Dtests=false -Ddocbook_docs=disabled -Dgtk_doc=false -Dman=false -Dintrospection=disabled" +compile = "PYTHONPATH=/usr/lib/python3.12/site-packages ninja -C output" +install = ''' +set -e +PYTHONPATH=/usr/lib/python3.12/site-packages DESTDIR=/out meson install -C output --no-rebuild + +# GUARDIÁN. Lo único que le importa a quien la consume es que exista el fichero CON EL NOMBRE QUE +# firefox abre por `dlopen`. Un artefacto con la librería bien construida pero con otro soname sería +# invisible para él y sellaría igual de contento — que es la forma de fallo que este frente ya se +# comió con `dbus` contra `dbus-shared`. Se exige el nombre exacto, no el directorio. +SO=/out/usr/lib/libnotify.so.4 +test -s "$SO" || { + echo "!! no está $SO — ¿cambió el soname upstream?" >&2 + find /out -name 'libnotify*' >&2 + exit 1 +} +# Y que sea de verdad una librería compartida: un enlace estático mal configurado deja el fichero +# con el nombre correcto y sin `SONAME`, o directamente un archivo `ar`. Se mira el ELF, no el nombre. +readelf -d "$SO" | grep -q 'SONAME.*libnotify.so.4' || { + echo "!! $SO no declara SONAME libnotify.so.4" >&2 + readelf -d "$SO" | head -20 >&2 + exit 1 +} +# `stat -Lc%s` y NO `stat -c%s`: `libnotify.so.4` es un SYMLINK a `libnotify.so.4.0.0`, así que sin +# la `-L` el guardián informaba «18 bytes» —el largo del nombre apuntado— y eso se lee como una +# librería vacía. El artefacto estaba bien y el mensaje mentía; se arregla el mensaje, que es lo que +# alguien va a leer a las tres de la mañana. Y se exige además el fichero REAL, porque un symlink +# colgante pasa `test -s` … no: `test -s` sigue el enlace, pero si el destino desapareciera el +# artefacto quedaría con dos nombres y ningún contenido. +REAL=/out/usr/lib/libnotify.so.4.0.0 +test -s "$REAL" || { echo "!! $SO apunta a la nada: no está $REAL" >&2; exit 1; } +echo "guardián: libnotify.so.4 compartida y con su SONAME ($(stat -Lc%s "$SO") bytes)" +''' + +[deps] +# `glib-shared` y `gdk-pixbuf-shared` y NO las estáticas: es la regla que el corpus paga desde el +# cuadro de las dos Pango — las deps de una `.so` van a las variantes compartidas, nunca junto a las +# estáticas, porque las dos instalan los mismos `.pc` y cabeceras. +# ⚠ `libpng-shared` está porque **las deps de hammer NO son transitivas**: el `.pc` de +# `gdk-pixbuf-2.0` declara `Requires: libpng`, y sin traerlo el `meson setup` muere con +# «Package 'libpng', required by 'gdk-pixbuf-2.0', not found» — un mensaje que nombra a gdk-pixbuf, +# que sí está, en vez de a lo que falta. Medido, no supuesto: es el primer intento de esta receta. +build = ["meson", "samurai", "python3", "pkgconf", + "glib-shared", "gdk-pixbuf-shared", "libpng-shared", + "libffi-shared", "pcre2-shared", "zlib-shared"] diff --git a/scripts/atuq-nested.sh b/scripts/atuq-nested.sh index c93f76c1..c16b6dbd 100755 --- a/scripts/atuq-nested.sh +++ b/scripts/atuq-nested.sh @@ -40,6 +40,7 @@ RAICES="atuq gcc-libs gtk3 atk gdk-pixbuf-shared pango-shared cairo-shared libep libjpeg-turbo-shared wayland libxkbcommon mesa libdrm dbus pipewire pulseaudio alsa-lib libxml2-shared dejavu-fonts bzip2-shared expat-shared ncurses-shared libsndfile libudev-zero zstd dbus-shared + libnotify xkeyboard-config grim" # ⚠ `gcc-libs` FALTABA, y su ausencia hacía MENTIR a esta prueba entera (2026-09-07). `atuq` declara # `NEEDED libstdc++.so.6` y `libgcc_s.so.1` —lo hereda de firefox, que va con clang++ y la libstdc++ diff --git a/scripts/test-atuq-rootfs.py b/scripts/test-atuq-rootfs.py index a86c805d..de0c0142 100755 --- a/scripts/test-atuq-rootfs.py +++ b/scripts/test-atuq-rootfs.py @@ -76,8 +76,7 @@ TRIAJE = { "libudev.so.0": ("ruido", "soname viejo; el corpus provee libudev.so.1 (libudev-zero)"), "libfreebl3.so": ("ruido", "NSS de este build expone libfreeblpriv3.so, que SÍ está"), # huecos de verdad: la función no existe en esta imagen - "libnotify.so.4": ("hueco", "NOTIFICACIONES WEB. Sin receta en el corpus"), - "libnotify.so.1": ("hueco", "idem, soname viejo"), + "libnotify.so.1": ("ruido", "soname viejo; el corpus provee libnotify.so.4 desde 2026-09-07"), "libsecret-1.so.0": ("hueco", "llavero: contraseñas al keyring. HAY receta en incoming-gnome"), "libcanberra.so.0": ("hueco", "sonidos del sistema. HAY receta en incoming-kde"), "libvulkan.so.1": ("hueco", "WebGPU/vulkan. HAY receta (vulkan-loader) en incoming-kde"),