Experimento decisivo: `gnome-shell --headless --virtual-monitor 1280x800`. El
backend headless crea el seat con META_SEAT_NATIVE_FLAG_NO_LIBINPUT, o sea que
saltea init_libinput() — justo el último paso del hilo de input antes de señalar
`input_thread_initialized` (meta-seat-impl.c:3098). Resultado:
libmutter-Message: Added virtual monitor Meta-0
libmutter-Message: Using Wayland display name 'wayland-0'
== compositor OK (wayland-0) — el shell ES el display server
⇒ **CONFIRMADO: el bloqueo del camino DRM está en libinput/udev, no en el resto
del arranque.** Todo lo demás de mutter funciona. Primer sospechoso libudev-zero.
Y con el compositor arriba aparece el muro siguiente, que es de otra naturaleza:
Gjs-CRITICAL: JS ERROR: Requiring AccountsService, version 1.0:
Typelib file for namespace 'AccountsService' not found
LA LECCIÓN DE MÉTODO: gnome-shell selló con su cierre de build COMPLETO (108/108)
y aun así la sesión muere pidiendo este typelib. **El cierre de build no es el
cierre de runtime**: todo lo que el shell carga por `imports.gi.*` desde
JavaScript es invisible al grafo de deps. Se encuentra ARRANCANDO, no compilando.
Se autoró recipes/incoming-gnome/accountsservice.toml. El configure pasa entero
—tres seds verificados contra el build real: generate-version.sh (deriva la
versión del nombre del DIRECTORIO, que en el sandbox es /src, y la rama git usa
`date`, o sea no-determinista), la aserción de wtmp (musl no define WTMPX_FILENAME
ni _PATH_WTMPX) y subdir('tests') (arrastra mocklibc, que llama fgetgrent, ausente
en musl)—. Ojo: -Dsystemdsystemunitdir tiene que ser literalmente `no`; con la
cadena vacía el meson interpreta "averiguá el directorio" y aserta pidiendo
systemd.pc.
NO SELLA todavía, y la frontera está contada símbolo por símbolo, no estimada:
· libelogind exporta 14 símbolos (los que pedía mutter) y accountsservice usa
OCHO que faltan: sd_get_sessions, sd_seat_can_multi_session,
sd_session_get_display y los cinco de sd_login_monitor_*. Estos últimos son
la parte con enjundia: no son getters sino una API de NOTIFICACIÓN (un fd
poll-able). Sobre el diseño actual el camino natural es inotify sobre
/run/systemd/{sessions,seats,users}.
· fgetspent_r no existe en musl (extensión glibc de /etc/shadow, usada en
src/daemon.c:265): necesita shim o parche a la variante no-reentrante.
Ninguno de los dos es de accountsservice: son huecos de NUESTRA capa de compat.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
252 lines
14 KiB
Bash
Executable File
252 lines
14 KiB
Bash
Executable File
#!/bin/sh
|
|
# gnome-start-qemu.sh — arranca gnome-shell (Wayland) en una VM QEMU con render por SOFTWARE
|
|
# (kms_swrast/llvmpipe) sobre el DRM de virtio-gpu. Análogo de plasma-start-qemu.sh, y hereda sus
|
|
# lecciones caras: el diagnóstico va POR SERIAL porque la ventana gtk se congela si mirada reinicia
|
|
# su Xwayland (QEMU sigue vivo por dentro). Ver [[kde-metal-qemu-desktop]].
|
|
#
|
|
# Se instala como /usr/bin/gnome-start y lo ejecuta DIRECTO el console-getty (no vía login shell).
|
|
# Salir: matar gnome-shell. Logs: /tmp/gnome-shell.log + tee al serial.
|
|
#
|
|
# DIFERENCIA CLAVE CON PLASMA: acá NO hay dos procesos (compositor + shell). gnome-shell ES el
|
|
# compositor — embebe mutter como librería (libmutter-16.so) y `--wayland` lo hace display server.
|
|
# Por eso no hay un equivalente de "kwin arriba pero plasmashell murió": si el shell cae, cae todo.
|
|
set -u
|
|
# getty ejecuta este script DIRECTO ⇒ PATH viene vacío. Primero eso, antes de invocar nada.
|
|
export PATH=/usr/bin:/bin:/usr/sbin:/sbin
|
|
# El modo lo hornea la imagen (GNOME_MODE=... en scripts/gnome/qemu-desktop-image.sh).
|
|
[ -r /etc/gnome-mode ] && . /etc/gnome-mode
|
|
say() { echo "== gnome-qemu :: $*" | tee -a /dev/console /dev/ttyS0 2>/dev/null; }
|
|
dump() { echo "---- $1 ----" > /dev/ttyS0 2>/dev/null; cat "$1" > /dev/ttyS0 2>/dev/null; }
|
|
|
|
export LD_LIBRARY_PATH=/usr/lib:/lib:/usr/lib/gnome-shell:/usr/lib/mutter-16
|
|
# GI_TYPELIB_PATH es EL env crítico de esta sesión, y el que no tiene análogo en KDE: gnome-shell es
|
|
# JavaScript sobre gjs y carga TODO por typelib. Los del sistema van en /usr/lib/girepository-1.0,
|
|
# pero St/Shell/Gvc/Shew se instalan APARTE en /usr/lib/gnome-shell (son privados del shell). Sin
|
|
# esta ruta, gjs no encuentra imports.gi.St y el shell muere en el primer require.
|
|
export GI_TYPELIB_PATH=/usr/lib/girepository-1.0:/usr/lib/gnome-shell:/usr/lib/gnome-shell/girepository-1.0
|
|
export XDG_DATA_DIRS=/usr/share
|
|
export XDG_RUNTIME_DIR=/run/user/0
|
|
export XDG_CONFIG_HOME=/root/.config
|
|
export XDG_CACHE_HOME=/root/.cache
|
|
export XDG_SESSION_TYPE=wayland
|
|
export HOME=/root
|
|
export GSETTINGS_SCHEMA_DIR=/usr/share/glib-2.0/schemas
|
|
export LANG=C.UTF-8
|
|
|
|
# --- render por software sobre virtio-gpu (los mismos fixes que costaron la campaña KDE) ---
|
|
export LIBGL_ALWAYS_SOFTWARE=1
|
|
export GALLIUM_DRIVER=llvmpipe
|
|
# GBM_ALWAYS_SOFTWARE=1 — virtio-gpu-pci SIN 3D expone un nodo KMS-only. Sin esto gbm carga kms_swrast
|
|
# pero deja gbm_dri->software=FALSE y dri2_initialize_drm entra al bloque `if(!software)` llamando
|
|
# queryCompatibleRenderOnlyDeviceFd(), NULL en el driver software ⇒ SIGSEGV. Fue el MURO 2 de KDE.
|
|
export GBM_ALWAYS_SOFTWARE=1
|
|
# kms_swrast y NO swrast: el compositor abre card0 como KMS y crea un gbm device encima ⇒ necesita el
|
|
# driver software PARA KMS (dumb buffers). Con 'swrast' pelado gbm_create_device falla.
|
|
export MESA_LOADER_DRIVER_OVERRIDE=kms_swrast
|
|
export LIBGL_DRIVERS_PATH=/usr/lib/dri
|
|
# llvmpipe SINCRÓNICO: su JIT multi-thread segfaultea al componer la 1ra superficie en esta VM.
|
|
export LP_NUM_THREADS=1
|
|
# Análogos mutter de dos fixes de kwin: cursor por software (el plano de cursor por HW no se presenta
|
|
# en virtio-gpu con render software ⇒ cursor invisible) y modeset simple en vez de atomic.
|
|
export MUTTER_DEBUG_DISABLE_HW_CURSORS=1
|
|
export MUTTER_DEBUG_FORCE_KMS_MODE=simple
|
|
# MUTTER_DEBUG_KMS_THREAD_TYPE=user: mutter corre su hebra KMS con prioridad de TIEMPO REAL por
|
|
# defecto. En esta VM no hay privilegios de RT scheduling, y el síntoma es exactamente el que dio el
|
|
# diagnóstico — hilo principal dormido en `futex_do_wait` con la "KMS thread" viva pero sin avanzar,
|
|
# el shell con /dev/dri/card0 ya abierto y ningún socket wayland creado nunca.
|
|
export MUTTER_DEBUG_KMS_THREAD_TYPE=user
|
|
# MUTTER_DEBUG_SEND_KMS_MODIFIERS=0: el equivalente en mutter del KWIN_DRM_USE_MODIFIERS=0 que costó
|
|
# la campaña KDE. virtio-gpu ANUNCIA soportar drmModeAddFB2WithModifiers pero RECHAZA el modifier
|
|
# explícito (aun LINEAR=0) con EINVAL ⇒ no se puede crear el framebuffer del compositor. Los nombres
|
|
# de estas variables están verificados contra los strings de libmutter-16.so.0, no de memoria.
|
|
export MUTTER_DEBUG_SEND_KMS_MODIFIERS=0
|
|
|
|
if [ "${DIAG:-1}" = 1 ]; then
|
|
export G_MESSAGES_DEBUG=all
|
|
export MUTTER_DEBUG=1
|
|
export EGL_LOG_LEVEL=debug
|
|
export LIBGL_DEBUG=verbose
|
|
fi
|
|
|
|
mkdir -p "$XDG_RUNTIME_DIR" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME" /run/dbus /var/lib/dbus /root
|
|
chmod 700 "$XDG_RUNTIME_DIR"
|
|
[ -s /etc/machine-id ] || cat /proc/sys/kernel/random/uuid 2>/dev/null | tr -d - > /etc/machine-id
|
|
cp -f /etc/machine-id /var/lib/dbus/machine-id 2>/dev/null || true
|
|
|
|
# gschemas.compiled: meson NO lo genera cuando DESTDIR está seteado (lo dice en el log del build:
|
|
# "Skipping custom install script because DESTDIR is set"). Sin él, GSettings aborta al primer
|
|
# g_settings_new() y el shell no llega ni a abrir el DRM. Se compila acá, en el primer arranque.
|
|
if [ ! -f "$GSETTINGS_SCHEMA_DIR/gschemas.compiled" ]; then
|
|
say "compilando $(ls $GSETTINGS_SCHEMA_DIR/*.xml 2>/dev/null | wc -l) esquemas GSettings..."
|
|
glib-compile-schemas "$GSETTINGS_SCHEMA_DIR" 2>/tmp/schemas.log \
|
|
&& say "esquemas OK" || { say "!! glib-compile-schemas FALLÓ:"; dump /tmp/schemas.log; }
|
|
fi
|
|
|
|
say "GPU DRM: $(ls /dev/dri/ 2>/dev/null | tr '\n' ' ')"
|
|
[ -e /dev/dri/card0 ] || say "!! no hay /dev/dri/card0 — ¿virtio-gpu no cargó?"
|
|
|
|
# D-Bus de SISTEMA: mutter habla con logind por el bus de sistema. En esta distro login1 lo provee
|
|
# arje-logind-compat; si no está, mutter cae a su ruta sin sesión (que es justo lo que libelogind
|
|
# vino a cubrir del lado de la C-ABI). El bus tiene que existir igual.
|
|
if [ ! -S /run/dbus/system_bus_socket ]; then
|
|
if dbus-daemon --system --fork 2>/tmp/dbus-system.log; then
|
|
say "system bus ON"
|
|
else
|
|
say "!! system bus NO arrancó:"; dump /tmp/dbus-system.log
|
|
fi
|
|
fi
|
|
|
|
# D-Bus de sesión: gnome-shell EXIGE uno (registra org.gnome.Shell y org.gnome.Mutter.*).
|
|
if [ -z "${DBUS_SESSION_BUS_ADDRESS:-}" ]; then
|
|
DBUS_SESSION_BUS_ADDRESS=$(dbus-daemon --session --fork --print-address 2>/dev/null)
|
|
export DBUS_SESSION_BUS_ADDRESS
|
|
fi
|
|
say "dbus sesión: ${DBUS_SESSION_BUS_ADDRESS:-NO ARRANCO}"
|
|
|
|
# arje-logind-compat: el login1 del fractal. Va DESPUÉS del bus de sistema (registra
|
|
# org.freedesktop.login1 ahí) y ANTES del shell. Mutter no sólo lo consulta por D-Bus: pregunta por
|
|
# SU sesión vía la C-ABI sd-login, que libelogind resuelve LEYENDO /run/systemd/sessions. Sin este
|
|
# daemon esos ficheros no existen y mutter muere en "Failed to find any matching session".
|
|
if [ -x /usr/bin/arje-logind-compat ]; then
|
|
# ARJE_LOGIN_STATE=1: la perilla que YA traía el daemon y que nadie prendía. Su comentario en
|
|
# arje-compat lo dice explícito — "en un host CON systemd real pisaría su estado; en arje (sin
|
|
# systemd) el launcher de sesión lo prende. Default off" — y el launcher de sesión es ESTE script.
|
|
# Con ella escribe /run/systemd/{sessions,seats,users}, que es lo que libelogind lee.
|
|
export ARJE_LOGIN_STATE=1
|
|
/usr/bin/arje-logind-compat >/tmp/logind-compat.log 2>&1 &
|
|
i=0
|
|
while [ $i -lt 40 ]; do
|
|
[ -d /run/systemd/sessions ] && [ -n "$(ls /run/systemd/sessions 2>/dev/null)" ] && break
|
|
i=$((i+1)); sleep 0.25
|
|
done
|
|
say "logind-compat: sesiones=$(ls /run/systemd/sessions 2>/dev/null | tr '\n' ' ') seats=$(ls /run/systemd/seats 2>/dev/null | tr '\n' ' ')"
|
|
[ -n "$(ls /run/systemd/sessions 2>/dev/null)" ] || dump /tmp/logind-compat.log
|
|
else
|
|
say "!! sin /usr/bin/arje-logind-compat — mutter no va a hallar sesión"
|
|
fi
|
|
|
|
# ── PUENTE EXPLÍCITO: el estado de sesión que arje-logind-compat todavía no escribe ─────────────
|
|
# ESTO ES ANDAMIO, NO LA SOLUCIÓN. arje-logind-compat adquiere org.freedesktop.login1 y sirve el
|
|
# Manager por D-Bus, pero NO deja nada en /run/systemd/{sessions,seats,users} — su handshake con el
|
|
# bus de arje falla ("Announce respuesta inesperada: identity mismatch"), así que nunca hay una
|
|
# sesión registrada por el fractal. Y mutter no pregunta por D-Bus: usa la C-ABI sd-login, que
|
|
# libelogind (arje-sdlogin-compat) resuelve LEYENDO esos ficheros. De ahí "Failed to find any
|
|
# matching session" aun con login1 arriba.
|
|
#
|
|
# Escribimos el estado a mano, en el formato que libelogind lee (claves sacadas del propio .so:
|
|
# LEADER/SEAT/CLASS/TYPE/ACTIVE/STATE/VTNR y CAN_GRAPHICAL del seat). Sirve para PROBAR que el resto
|
|
# de la pila funciona; el arreglo de verdad es que arje-logind-compat registre la sesión del fractal
|
|
# —o que el Announce deje de fallar— y escriba esto solo. Queda marcado para no confundir andamio
|
|
# con producto.
|
|
if [ -z "$(ls /run/systemd/sessions 2>/dev/null)" ]; then
|
|
say "puente: escribiendo estado de sesión a mano (arje-logind-compat no lo hizo)"
|
|
mkdir -p /run/systemd/sessions /run/systemd/seats /run/systemd/users
|
|
cat > /run/systemd/sessions/1 <<EOF
|
|
UID=0
|
|
NAME=root
|
|
LEADER=$$
|
|
SEAT=seat0
|
|
TYPE=wayland
|
|
CLASS=user
|
|
ACTIVE=1
|
|
STATE=active
|
|
VTNR=1
|
|
REMOTE=0
|
|
EOF
|
|
cat > /run/systemd/seats/seat0 <<EOF
|
|
CAN_GRAPHICAL=1
|
|
CAN_MULTI_SESSION=1
|
|
ACTIVE=1
|
|
ACTIVE_SESSIONS=1
|
|
SESSIONS=1
|
|
EOF
|
|
cat > /run/systemd/users/0 <<EOF
|
|
NAME=root
|
|
STATE=active
|
|
SESSIONS=1
|
|
ACTIVE_SESSIONS=1
|
|
EOF
|
|
fi
|
|
# XDG_SESSION_ID/SEAT/VTNR: libelogind los menciona entre sus símbolos, y son además el contrato
|
|
# estándar por el que un proceso sabe cuál es SU sesión sin consultar por PID.
|
|
export XDG_SESSION_ID=1
|
|
export XDG_SEAT=seat0
|
|
export XDG_VTNR=1
|
|
|
|
if [ "${DIAG:-1}" = 1 ]; then
|
|
ulimit -c unlimited 2>/dev/null || true
|
|
echo '/core.%e.%p' > /proc/sys/kernel/core_pattern 2>/dev/null || true
|
|
fi
|
|
|
|
say "typelibs visibles: $(ls /usr/lib/girepository-1.0/*.typelib 2>/dev/null | wc -l) sistema + $(ls /usr/lib/gnome-shell/*.typelib 2>/dev/null | wc -l) del shell"
|
|
# GNOME_MODE=headless — EXPERIMENTO DECISIVO, no un modo de producción. El backend headless de
|
|
# mutter crea el seat con META_SEAT_NATIVE_FLAG_NO_LIBINPUT, o sea que SALTEA `init_libinput()`,
|
|
# que es justo el último paso del hilo de input antes de señalar `input_thread_initialized`
|
|
# (meta-seat-impl.c:3098). Si en headless aparece wayland-0, el bloqueo está en libinput/udev y no
|
|
# en el resto del arranque; si tampoco aparece, el problema es anterior (keymap, input settings).
|
|
case "${GNOME_MODE:-drm}" in
|
|
headless) SHELL_ARGS="--headless --virtual-monitor 1280x800" ;;
|
|
*) SHELL_ARGS="--wayland" ;;
|
|
esac
|
|
say "lanzando gnome-shell $SHELL_ARGS (modo=${GNOME_MODE:-drm})..."
|
|
/usr/bin/gnome-shell $SHELL_ARGS >/tmp/gnome-shell.log 2>&1 &
|
|
SHELL_PID=$!
|
|
# stream EN VIVO al serial (busybox sed no soporta -u ⇒ tail -f directo, sin pipes).
|
|
( echo "==== gnome-shell.log (stream vivo) ===="; tail -f /tmp/gnome-shell.log ) > /dev/ttyS0 2>/dev/null &
|
|
|
|
# OJO con el test de vivo: `kill -0` TIENE ÉXITO sobre un ZOMBI (el hijo existe como PID hasta que
|
|
# el padre lo cosecha, y acá el padre es este script, que sólo hace wait al final). La primera
|
|
# versión usaba kill -0 y por eso reportó "sin wayland-0 tras 45s" cuando en realidad gnome-shell ya
|
|
# había muerto en el primer segundo: el diagnóstico decía "0 hilos" porque no quedaba proceso, sólo
|
|
# la entrada zombi. Se mira el State de /proc en su lugar.
|
|
alive() { s=$(sed -n 's/^State:[[:space:]]*//p' /proc/$1/status 2>/dev/null); case "$s" in ""|Z*) return 1;; *) return 0;; esac; }
|
|
i=0
|
|
while [ $i -lt 240 ]; do
|
|
[ -S "$XDG_RUNTIME_DIR/wayland-0" ] && break
|
|
alive $SHELL_PID || { wait $SHELL_PID; RC=$?; say "!! gnome-shell murió (código $RC) antes de exponer wayland-0:"; dump /tmp/gnome-shell.log; say "cores: $(ls /core.* 2>/dev/null | tr '\n' ' ' || echo NINGUNO)"; exec /bin/sh; }
|
|
i=$((i+1)); sleep 0.5
|
|
done
|
|
if [ -S "$XDG_RUNTIME_DIR/wayland-0" ]; then
|
|
say "compositor OK (wayland-0) — el shell ES el display server"
|
|
else
|
|
say "!! sin wayland-0 tras 120s:"; dump /tmp/gnome-shell.log
|
|
# El shell NO murió: quedó COLGADO. Sin gdb en la imagen, el diagnóstico barato es dónde está
|
|
# bloqueado el hilo principal (wchan) y contra qué socket, que es lo que distingue "esperando una
|
|
# respuesta D-Bus que no llega" de "spinning en el renderer".
|
|
say "--- diagnóstico del cuelgue (pid $SHELL_PID) ---"
|
|
say "XDG_RUNTIME_DIR ($XDG_RUNTIME_DIR): $(ls -a $XDG_RUNTIME_DIR 2>/dev/null | tr '\n' ' ')"
|
|
say "sockets wayland en el sistema: $(find / -name 'wayland-*' -maxdepth 6 2>/dev/null | tr '\n' ' ')"
|
|
say "wchan: $(cat /proc/$SHELL_PID/wchan 2>/dev/null)"
|
|
say "state: $(grep -a ^State /proc/$SHELL_PID/status 2>/dev/null)"
|
|
say "stack principal:"; cat /proc/$SHELL_PID/stack > /dev/ttyS0 2>/dev/null
|
|
say "syscall: $(cat /proc/$SHELL_PID/syscall 2>/dev/null)"
|
|
say "fds abiertos:"; ls -l /proc/$SHELL_PID/fd 2>/dev/null > /dev/ttyS0
|
|
say "hilos: $(ls /proc/$SHELL_PID/task 2>/dev/null | wc -l)"
|
|
# Sin gdb en la imagen, la única forma de ver DÓNDE está bloqueado es forzarle un core y leerlo
|
|
# con el gdb del host (`thread apply all bt`). SIGABRT lo produce y no lo maneja nadie.
|
|
say "forzando core del proceso colgado (SIGABRT)..."
|
|
kill -ABRT $SHELL_PID 2>/dev/null
|
|
sleep 12; sync; sync
|
|
say "cores: $(ls -la /core.* 2>/dev/null | tr '\n' ' ' || echo NINGUNO)"
|
|
for t in $(ls /proc/$SHELL_PID/task 2>/dev/null); do
|
|
say " tid $t wchan=$(cat /proc/$SHELL_PID/task/$t/wchan 2>/dev/null) comm=$(cat /proc/$SHELL_PID/task/$t/comm 2>/dev/null)"
|
|
done
|
|
exec /bin/sh
|
|
fi
|
|
export WAYLAND_DISPLAY=wayland-0
|
|
|
|
( n=0; while sleep 15; do
|
|
n=$((n+1))
|
|
say "STATUS +$((n*15))s: gnome-shell=$(pgrep gnome-shell 2>/dev/null | wc -l)"
|
|
done ) &
|
|
|
|
wait $SHELL_PID
|
|
RC=$?
|
|
say "gnome-shell salió (código $RC) — volcando log:"
|
|
dump /tmp/gnome-shell.log
|
|
cp /tmp/gnome-shell.log /gnome-shell.log 2>/dev/null || true
|
|
say "cores en /: $(ls -la /core.* 2>/dev/null | tr '\n' ' ' || echo NINGUNO)"
|
|
sync; sync
|
|
say "== disco SYNCED. serial CONGELADO. matá QEMU desde el host. =="
|
|
sleep 3600
|