Arch Linux en WSL2: anatomía y recuperación de un E_UNEXPECTED

Resultado

Arch seguía registrada en WSL2, pero cualquier intento de iniciarla terminaba en Wsl/Service/E_UNEXPECTED. El mensaje de Windows escondía un fallo bastante más concreto dentro de Linux: systemd y pacman no podían cargar varias bibliotecas ELF truncadas a cero bytes, y parte de /var/lib/pacman/local había perdido sus metadatos. La distribución se recuperó sin desregistrarla ni sustituir su VHDX: copia de seguridad, acceso mediante wsl --system, restauración de OpenSSL y acl, reparación de la base local y actualización completa.

Alcance

La corrupción técnica quedó demostrada; su desencadenante exacto, no. La actualización reciente de ArchWSL y los ~4 GB libres que quedaban en C: son antecedentes plausibles, pero esta nota no los convierte en causa sin evidencia.

Lo interesante del caso no es solo la secuencia de comandos. Es el cambio de perspectiva: E_UNEXPECTED parecía un error opaco de Windows hasta que se separó la capa WSL de la distribución y se consiguió ejecutar el sistema desde fuera.

El sistema detrás del error

WSL2 no guarda Arch como una carpeta corriente. Windows registra la distribución, WSL arranca una máquina virtual ligera y el sistema Linux vive dentro de un VHDX con ext4. En este equipo, Arch tenía además systemd=true, así que WSL debía poder ejecutar /usr/lib/systemd/systemd como PID 1 antes de entregar una shell.

flowchart LR
    WIN["Windows 11"] --> WSL["Servicio WSL2"]
    WSL --> VM["VM ligera + kernel Linux"]
    VM --> VHDX["ext4.vhdx"]
    VHDX --> ROOT["Raíz de Arch"]
    ROOT --> PID1["systemd · PID 1"]
    PID1 --> SHELL["Bash / sesión de usuario"]
    ROOT --> PACMAN["Pacman + base local"]
    ROOT --> ELF["Loader y bibliotecas ELF"]
    ELF --> PID1
    ELF --> PACMAN

Este mapa permite leer el síntoma con más precisión. Si otra distribución arranca, la VM y el kernel siguen vivos. Si Arch falla incluso como root y sin perfiles, el problema aparece antes de la shell. Y si un chroot muestra libcrypto.so.3: file too short, el error genérico ya tiene una causa observable: el proceso que WSL necesita arrancar no puede resolver sus dependencias.

Flujo técnico de diagnóstico y recuperación

flowchart TD
    A["E_UNEXPECTED"] --> B{"¿Otra distro inicia?"}
    B -- "No" --> C["Diagnosticar host WSL"]
    B -- "Sí" --> D["Aislar Arch"]
    D --> E["Backup del VHDX"]
    E --> F["wsl --system + chroot"]
    F --> G["Bibliotecas a 0 bytes"]
    G --> H["Restaurar OpenSSL y acl"]
    H --> I["Pacman vuelve a ejecutar"]
    I --> J["Reconstruir base local"]
    J --> K["pacman -Syu + Qkk"]
    K --> L["Reinicio y validación"]

1. Alcance

Este documento describe la recuperación concreta de una distribución Arch Linux sobre WSL2 con las siguientes propiedades:

WSL:              2.7.11.0
Kernel:           6.18.33.2-2
WSLg:             1.0.73.2
Windows:          10.0.26200.8875
Distribución:     Arch
Otra distribución: docker-desktop
systemd en Arch:  habilitado

VHDX:

C:\Applications\Scoop\persist\archwsl\data\ext4.vhdx

Tamaño físico observado:

96,63 GiB

Capacidad virtual observada:

1 TiB

2. Síntoma inicial

wsl -d Arch
Error catastrófico
Código de error: Wsl/Service/E_UNEXPECTED

También fallaba:

wsl -d Arch -u root --exec /usr/bin/bash --noprofile --norc

Por tanto, el fallo no dependía de:

  • Usuario predeterminado.
  • Zsh.
  • .bashrc.
  • .zshrc.
  • Perfil interactivo.
  • Lanzador Arch.exe.

3. Hipótesis iniciales

HipótesisPruebaResultado
WSL roto globalmenteArrancar docker-desktopDescartada
Servicio WSL bloqueadoReiniciar WslService, vmcompute, hnsSin efecto
Nuevo Arch.exe defectuosoUsar wsl -d Arch directamenteNo era la causa
Usuario o shell dañadosForzar root y Bash sin perfilesDescartada
VHDX ausenteInspeccionar registro y rutaDescartada
ext4 gravemente corruptoe2fsckSin bloques defectuosos
PID 1 o dependencia ELF rotachroot y ejecutar systemdConfirmada

4. Confirmar que WSL funciona

wsl --shutdown
wsl --version
wsl --status
wsl -l -v

Prueba de control:

wsl -d docker-desktop -u root --exec /bin/sh -c "echo WSL_VM_OK"

Resultado:

WSL_VM_OK

Interpretación

Si otra distribución arranca, la infraestructura WSL2 está operativa. El análisis debe desplazarse a:

  • Registro específico de la distribución.
  • VHDX.
  • Sistema de archivos.
  • PID 1.
  • Dependencias dinámicas.
  • Base de paquetes.

5. Localizar y respaldar el VHDX

Consulta del registro:

$archKey = Get-ChildItem `
  'HKCU:\Software\Microsoft\Windows\CurrentVersion\Lxss' |
Where-Object {
  $_.GetValue('DistributionName') -eq 'Arch'
}
 
$base = [Environment]::ExpandEnvironmentVariables(
  $archKey.GetValue('BasePath')
)
 
$vhd = Join-Path $base 'ext4.vhdx'
 
Get-Item -LiteralPath $vhd |
  Select-Object FullName,
    @{N='GiB';E={[math]::Round($_.Length / 1GB, 2)}},
    LastWriteTime

Antes de cualquier reparación:

wsl --shutdown
 
Copy-Item `
  -LiteralPath $vhd `
  -Destination 'D:\WSL-Backups\Arch-ext4-20260805.vhdx'

Danger

No ejecutar wsl --unregister Arch. Esa operación elimina la distribución registrada y sus datos.

6. Inspección mediante wsl --system

wsl --system -d Arch -u root -- sh -lc '
df -h /mnt/wslg/distro
mount | grep /mnt/wslg/distro
 
ls -l /mnt/wslg/distro/bin/sh
ls -l /mnt/wslg/distro/usr/bin/bash
ls -l /mnt/wslg/distro/usr/lib/systemd/systemd
ls -l /mnt/wslg/distro/usr/lib/ld-linux-x86-64.so.2
 
cat /mnt/wslg/distro/etc/wsl.conf
'

Configuración encontrada:

[boot]
systemd=true
 
[network]
generateResolvConf=false

7. Comprobación de ext4

Adjuntar el VHDX:

$vhd = 'C:\Applications\Scoop\persist\archwsl\data\ext4.vhdx'
 
wsl --shutdown
wsl --mount "$vhd" --vhd --bare

Identificar el dispositivo desde otra distribución:

wsl -d docker-desktop -u root -- \
  lsblk -o NAME,PATH,SIZE,FSTYPE,RO,MOUNTPOINTS

Resultado relevante:

sdc  /dev/sdc  1T  ext4  0

Reparación:

wsl -d docker-desktop -u root -- e2fsck -f -v /dev/sdc

Resultado:

FILE SYSTEM WAS MODIFIED
0 bad blocks

Después:

wsl --unmount
wsl --shutdown

Arch continuaba sin arrancar. e2fsck no era la solución principal.

8. Obtener el error Linux real mediante chroot

wsl --system -d Arch -u root -- \
  chroot /mnt/wslg/distro /bin/bash --noprofile --norc -lc '
echo CHROOT_OK
/usr/lib/systemd/systemd --version
pacman --version
'

Resultado:

CHROOT_OK
/usr/lib/systemd/systemd: error while loading shared libraries:
/usr/lib/libcrypto.so.3: file too short
 
pacman: error while loading shared libraries:
/usr/lib/libcrypto.so.3: file too short

Comprobación:

/usr/lib/libcrypto.so.3 — 0 bytes
/usr/lib/libssl.so.3    — 0 bytes

Diagnóstico

systemd no podía cargar OpenSSL. Al no poder ejecutar el PID 1 configurado, WSL devolvía el error genérico E_UNEXPECTED.

9. Restaurar OpenSSL desde la caché de Pacman

Paquetes encontrados:

openssl-3.6.1-1-x86_64.pkg.tar.zst
openssl-3.6.2-2-x86_64.pkg.tar.zst
openssl-3.6.3-1-x86_64.pkg.tar.zst

Como docker-desktop no incluía bsdtar, zstd ni unzstd, se copió el paquete a Windows:

@'
set -eu
 
ROOT=/mnt/arch-repair
DEVICE=/dev/sdc
DEST=/mnt/host/c/Temp/arch-openssl-repair
 
mkdir -p "$ROOT" "$DEST"
mount -t ext4 -o rw "$DEVICE" "$ROOT"
 
PKG="$(ls -1t \
  "$ROOT"/var/cache/pacman/pkg/openssl-*-x86_64.pkg.tar.zst |
  head -n 1)"
 
cp "$PKG" "$DEST/openssl.pkg.tar.zst"
 
sync
umount "$ROOT"
'@ | wsl -d docker-desktop -u root -- sh -s

Extracción en Windows:

$repair = 'C:\Temp\arch-openssl-repair'
$pkg = "$repair\openssl.pkg.tar.zst"
$extract = "$repair\extract"
 
New-Item -ItemType Directory -Path $extract -Force | Out-Null
 
tar.exe -xpf $pkg `
  -C $extract `
  usr/lib/libcrypto.so.3 `
  usr/lib/libssl.so.3

Restauración en el VHDX:

@'
set -eu
 
ROOT=/mnt/arch-repair
DEVICE=/dev/sdc
SRC=/mnt/host/c/Temp/arch-openssl-repair/extract/usr/lib
 
mkdir -p "$ROOT"
mount -t ext4 -o rw "$DEVICE" "$ROOT"
 
cp -f "$SRC/libcrypto.so.3" "$ROOT/usr/lib/libcrypto.so.3"
cp -f "$SRC/libssl.so.3"    "$ROOT/usr/lib/libssl.so.3"
 
chown 0:0 \
  "$ROOT/usr/lib/libcrypto.so.3" \
  "$ROOT/usr/lib/libssl.so.3"
 
chmod 755 \
  "$ROOT/usr/lib/libcrypto.so.3" \
  "$ROOT/usr/lib/libssl.so.3"
 
sync
 
stat -c '%n — %s bytes' \
  "$ROOT/usr/lib/libcrypto.so.3" \
  "$ROOT/usr/lib/libssl.so.3"
 
od -An -tx1 -N4 "$ROOT/usr/lib/libcrypto.so.3"
 
umount "$ROOT"
'@ | wsl -d docker-desktop -u root -- sh -s

Resultado:

libcrypto.so.3 — 5922312 bytes
libssl.so.3    — 1011672 bytes
7f 45 4c 46

7f 45 4c 46 es la cabecera ELF.

10. Segundo fallo: libacl.so.1

Después de recuperar OpenSSL:

/usr/bin/pacman: error while loading shared libraries:
/usr/lib/libacl.so.1: file too short

Se descubrió que libacl.so.1 también estaba truncada.

11. Recuperación con pacman-static

Se copió un binario estático a:

C:\Temp\arch-repair\pacman-static

Problemas iniciales:

GPGME error: Invalid crypto engine
failed to synchronize all databases
unable to lock database

Configuración temporal

Se usó exclusivamente para paquetes locales ya descargados:

[options]
Architecture = x86_64
DBPath = /var/lib/pacman/
CacheDir = /var/cache/pacman/pkg/
LogFile = /var/log/pacman.log
SigLevel = Never
LocalFileSigLevel = Never
RemoteFileSigLevel = Never

Warning

Esta configuración no debe utilizarse para una actualización general desde repositorios. Se empleó solo para recuperar paquetes ya presentes en la caché y romper la dependencia circular.

El bloqueo se eliminó:

rm -f /var/lib/pacman/db.lck

Se reinstaló acl desde la caché:

pacman-static \
  --config /tmp/pacman-recovery.conf \
  -U \
  --noconfirm \
  --noscriptlet \
  --overwrite '*' \
  /var/cache/pacman/pkg/acl-2.4.0-1-x86_64.pkg.tar.zst

Resultado:

Pacman v7.1.0
systemd 260

Arch volvió a iniciar:

wsl -d Arch -u root --exec /bin/bash --noprofile --norc
bash-5.3#

12. Reconstruir Bash en la base local

Estado inicial:

pacman -Q bash
pacman -Qo /usr/bin/bash
ls -la /var/lib/pacman/local/bash-*

Resultado:

bash 5.3.15-1
No package owns /usr/bin/bash
 
desc  → 0 bytes
files → 0 bytes

Reinstalación:

rm -f /var/lib/pacman/db.lck
pacman -S --overwrite '*' bash

Validación:

pacman -Qo /usr/bin/bash
pacman -Qkk bash
/usr/bin/bash is owned by bash 5.3.15-1
bash: 270 total files, 0 altered files

13. Reconstruir Ansible y Ansible Core

Entrada dañada de Ansible:

/var/lib/pacman/local/ansible-14.2.0-1/desc

Se apartó:

mkdir -p /root/pacman-db-recovery
 
mv /var/lib/pacman/local/ansible-14.2.0-1 \
  /root/pacman-db-recovery/ansible-14.2.0-1.broken

Reinstalación:

pacman -S --overwrite '*' ansible

Validación:

pacman -Qkk ansible
ansible: 51231 total files, 0 altered files

/usr/bin/ansible seguía sin propietario porque pertenece a ansible-core. Su entrada también tenía desc y files a cero.

mv /var/lib/pacman/local/ansible-core-2.21.2-1 \
  /root/pacman-db-recovery/ansible-core-2.21.2-1.broken
 
pacman -S --overwrite '*' ansible-core

Validación:

/usr/bin/ansible is owned by ansible-core 2.21.2-1
ansible-core: 3068 total files, 0 altered files

14. Reconstrucción masiva de /var/lib/pacman/local

Se detectaron muchas entradas con:

desc=0
files=0

Afectaban, entre otros, a:

  • Paquetes Perl.
  • Paquetes Python.
  • 7zip.
  • pacman-contrib.
  • plocate.
  • powerline.
  • Herramientas de seguridad.
  • Dependencias auxiliares.

Criterio correcto de corrupción

Una entrada es sospechosa cuando:

desc ausente o vacío

Un archivo files vacío no implica por sí solo corrupción. Algunos metapaquetes legítimos no contienen archivos.

Ejemplos válidos:

base: 0 total files, 0 altered files
base-devel: 0 total files, 0 altered files
ca-certificates: 0 total files, 0 altered files
gcc-libs: 0 total files, 0 altered files
linux-firmware: 0 total files, 0 altered files
texlive-meta: 0 total files, 0 altered files

Esquema de reconstrucción

set -euo pipefail
 
STAMP="$(date +%Y%m%d-%H%M%S)"
RECOVERY="/root/pacman-db-recovery/$STAMP"
LOCAL_DB="/var/lib/pacman/local"
CACHE="/var/cache/pacman/pkg"
 
mkdir -p "$RECOVERY/entries"
 
tar -C /var/lib/pacman \
  -czf "$RECOVERY/local-before-repair.tar.gz" \
  local

Para cada entrada con desc vacío:

  1. Inferir el nombre del paquete.
  2. Mover la entrada a $RECOVERY/entries.
  3. Reinstalar desde repositorio si existe.
  4. Si no, buscar el paquete en /var/cache/pacman/pkg.
  5. Conservar una lista de pendientes.

El proceso dejó solo seis paquetes con files=0, todos verificados como legítimos metapaquetes.

15. Actualización completa

Después de reparar Bash y la base local:

rm -f /var/lib/pacman/db.lck
pacman -Syu

La primera ejecución detectó 509 actualizaciones, pero falló por conflictos de Bash antes de que su registro fuese reconstruido.

Después de reparar las entradas afectadas, la actualización pudo completarse.

16. Auditoría de archivos vacíos

Una búsqueda general:

find /usr/bin /usr/lib -type f -size 0

produce mucho ruido legítimo:

  • __init__.py
  • py.typed
  • .keep
  • .stamp
  • fixtures
  • datos de prueba
  • archivos de bloqueo
  • marcadores

La auditoría útil es:

echo '=== EJECUTABLES VACÍOS ==='
 
find /usr/bin \
  -xdev \
  -type f \
  -size 0 \
  -perm /111 \
  -print
 
echo '=== BIBLIOTECAS COMPARTIDAS VACÍAS ==='
 
find /usr/lib \
  -xdev \
  -type f \
  -size 0 \
  \( -name '*.so' -o -name '*.so.*' \) \
  -print

17. Restos huérfanos de Protobuf

Se encontraron:

/usr/bin/protoc-33.1.0
/usr/bin/protoc-gen-upb-33.1.0
/usr/bin/protoc-gen-upb_minitable-33.1.0
/usr/bin/protoc-gen-upbdefs-33.1.0

Características:

0 bytes
sin propietario Pacman

La versión vigente estaba íntegra:

protobuf 35.1-1
protobuf: 385 total files, 0 altered files

Archivos actuales:

/usr/bin/protoc-35.1.0
/usr/bin/protoc-gen-upb-35.1.0
/usr/bin/protoc-gen-upb_minitable-35.1.0
/usr/bin/protoc-gen-upbdefs-35.1.0

Los cuatro restos 33.1.0 se eliminaron.

18. Validaciones finales

Base local

for d in /var/lib/pacman/local/*; do
  [[ -d "$d" ]] || continue
 
  entry="${d##*/}"
  [[ "$entry" == "ALPM_DB_VERSION" ]] && continue
 
  if [[ ! -s "$d/desc" ]]; then
    echo "DAÑADO: $entry"
  fi
done

Resultado esperado:

sin salida

Paquetes reparados

pacman -Qkk bash
pacman -Qkk openssl
pacman -Qkk acl
pacman -Qkk ansible
pacman -Qkk ansible-core
pacman -Qkk protobuf

Ejecutables esenciales

pacman --version
systemctl --version
protoc --version

Reinicio

exit
wsl --shutdown
wsl -d Arch

19. Causa raíz y nivel de certeza

Causa raíz técnica confirmada

  • Bibliotecas ELF críticas a cero bytes.
  • Registros desc y files de la base local de Pacman vacíos o ausentes.
  • Archivos antiguos huérfanos a cero bytes.
  • Coherencia rota entre sistema de archivos y base local de paquetes.

Causa desencadenante probable

Una operación de actualización o escritura quedó interrumpida y creó una corrupción parcial y localizada.

No demostrado

No se demostró que la actualización de ArchWSL 25.3.19.0 → 26.4.2.0 causara la corrupción. Fue una correlación temporal, no una relación causal probada.

20. Árbol de decisión reutilizable

wsl -d DISTRO falla

├─ ¿Otra distribución WSL arranca?
│  ├─ No → revisar WSL, virtualización y servicios Windows
│  └─ Sí → fallo aislado a DISTRO

├─ ¿Arranca como root y shell mínimo?
│  ├─ Sí → usuario, shell o perfiles
│  └─ No → fallo anterior al proceso solicitado

├─ ¿wsl --system permite leer la raíz?
│  ├─ No → VHDX, ruta, permisos o ext4
│  └─ Sí → inspeccionar PID 1 y dependencias

├─ ¿chroot ejecuta Bash?
│  ├─ No → glibc, loader o Bash
│  └─ Sí → ejecutar systemd y pacman

├─ ¿Aparece "file too short"?
│  ├─ Sí → localizar archivo, medirlo y restaurar paquete
│  └─ No → revisar logs de WSL y configuración

├─ ¿pacman puede ejecutarse?
│  ├─ No → restaurar bibliotecas o usar pacman-static
│  └─ Sí → validar /var/lib/pacman/local

├─ ¿desc está vacío o ausente?
│  ├─ Sí → apartar entrada y reinstalar paquete
│  └─ No → ejecutar pacman -Qkk

└─ Auditar ejecutables y bibliotecas vacías

21. Comandos que deben evitarse durante el diagnóstico

wsl --unregister Arch
scoop uninstall archwsl

Y, salvo reparación controlada:

pacman -Syu --overwrite '*'

22. Medidas preventivas

  1. Exportar periódicamente la distribución:

    wsl --export Arch D:\WSL-Backups\Arch-YYYYMMDD.tar
  2. Mantener una copia apagada del ext4.vhdx.

  3. No interrumpir pacman -Syu.

  4. Comprobar espacio libre antes de grandes actualizaciones:

    df -h /
  5. Revisar el log de Pacman:

    tail -n 200 /var/log/pacman.log
  6. Auditar la base local tras una recuperación:

    pacman -Qkk
  7. Conservar paquetes recientes en /var/cache/pacman/pkg.

  8. Tener disponible una distribución auxiliar WSL o un binario pacman-static.

Relacionado