Hace unos días, el compañero Jinkros publicó una guía sobre cómo arrancar entre varias imágenes ISO en una memoria USB sobre BIOS y UEFI usando tan solo GRUB2.
Muchos sabréis que esto implica tener que realizar reinicios una y otra vez en tu ordenador para asegurarse que las imágenes ISO arranquen correctamente desde el gestor de arranque que le hemos añadido a la memoria USB, cosa que puede ser un lastre para el usuario y hasta puede ser un castigo para los discos duros mecánicos que tengas conectados en el equipo, pues es el componente que más se puede dañar en un ordenador entre repetidos reinicios.
¿Hay alguna solución más inmediata que no requiera ni siquiera reiniciar el ordenador y reducir el riesgo de acortar la vida de algún componente? Por supuesto, y esa solución se llama virtualización. En esta guía veremos cómo crear y particionar una imagen de disco en crudo (RAW) para poder luego instalar GRUB2 sobre ella y realizar en esta misma imagen de disco las pruebas que nos hagan falta con ayuda del virtualizador QEMU, para cerciorarnos de que tengamos el menú de GRUB2 correctamente configurado para poder arrancar las distribuciones que tengamos en las imágenes ISO tanto con arranque BIOS como con arranque EFI, y así luego transferir los archivos que hayamos cambiado definitivamente a la memoria USB.
Lo primero que vamos a hacer es reservar un poco de espacio en nuestro disco duro para crear una imagen lo suficientemente grande como para que quepa al menos una imagen ISO de cualquier distribución que queramos probar. Esta imagen de disco nos servirá como si de un disco duro virtual se tratase, pero sin ningún formato especial, de forma que podamos montar su(s) sistema(s) de archivos directamente en el sistema, o incluso clonarlos junto con su tabla de particiones en otro dispositivo físico de almacenamiento.
La imagen la podemos crear con fallocate (inmediato, compatible con algunos sistemas de archivos POSIX) o bien con dd (lento). Nosotros por ejemplo vamos a crear una imagen de 8 GiB que llamaremos imagen_prueba.img nos imaginaremos que conceptualmente se trata de un archivo de pendrive virtual. Para ello tendremos que introducir en la terminal uno de los dos comandos:
1 2 |
$ fallocate -l 8G imagen_prueba.img # Opción 1 $ dd if=/dev/zero of=imagen_prueba.img count=8192 bs=1M # Opción 2 |
Con esto ya tenemos un archivo de 8 GiB que no contiene por el momento ninguna información:
1 2 |
$ ls -l imagen_prueba.img -rw-r--r-- 1 alex users 8G jun 5 16:11 imagen_prueba.img |
Hasta ahora los comandos introducidos no requerían privilegios de superusuario. A partir de ahora y hasta nuevo aviso, los siguientes comandos serán introducidos como superusuario (se distinguirán no obstante si van precedidos o no de un "#" en el prompt). Puedes sustituir a este usuario con el comando su o con sudo -s si la cuenta de root está bloqueada.
En estos momentos la imagen está totalmente vacía y sin sistemas de archivos, por lo que no podremos montarla todavía para formatearla, y para eso necesitamos antes que cuente con una tabla de particiones.
Podemos particionar la imagen directamente con herramientas conocidas como fdisk, cfdisk o parted, pero el problema de hacerlo en caliente vendrá cuando queramos formatear las particiones que hayamos creado, que no lo podremos hacer si antes no tenemos la imagen asociada como un dispositivo de almacenamiento más en el sistema.
Para crear un dispositivo de bucle (loop) asociada a nuestra imagen de disco recién creada, usaremos la herramienta losetup. Para ello, basta con introducir el siguiente comando:
1 |
# losetup /dev/loop0 imagen_prueba.img |
Tal vez nos devolverá un error diciendo que el dispositivo o recurso está ocupado, tal vez porque el bloque /dev/loop0 ya esté ocupado por otro archivo. En tal caso, probamos con otro número de dispositivo (hasta loop7), o bien desmontamos el dispositivo /dev/loop0 para poder liberarlo tras introducir el comando losetup -d /dev/loop0 como superusuario.
Si en cambio no devuelve ninguna salida, quiere decir que todo ha salido bien. Ahora tendremos el archivo imagen_prueba.img que hemos creado antes reflejado en el bloque de dispositivo /dev/loop0. Para asegurarnos de que es así, lanzaremos cat /proc/partitions para ver todos los sistemas de archivos localizados:
1 2 3 4 5 6 7 8 9 |
# cat /proc/partitions major minor #blocks name 8 0 488386584 sda 8 1 41943040 sda1 8 2 438052445 sda2 8 3 8387591 sda3 11 0 1048575 sr0 7 0 8388608 loop0 |
Lo que nos queda por hacer ahora es particionar la imagen como si de un pendrive real se tratase con el editor de particiones GNU parted. Abrimos el dispositivo de bucle con parted y escribimos el comando "p" dentro del prompt del editor:
1 2 3 4 5 6 7 8 9 10 11 12 |
# parted /dev/loop0 GNU Parted 3.2 Usando /dev/loop0 Welcome to GNU Parted! Type 'help' to view a list of commands. (parted) p Error: /dev/loop0: unrecognised disk label Model: Loopback device (loopback) Disk /dev/loop0: 8590MB Sector size (logical/physical): 512B/512B Partition Table: unknown Disk Flags: (parted) |
No nos preocuparemos por el error que nos da, ya que nos dice que no se reconoce la etiqueta del disco (es normal, ahora mismo la imagen está totalmente vacía). Por lo que sí que nos tendremos que preocupar es por las unidades, ya que por defecto parted está configurado para medir los tamaños de discos y de particiones en megabytes (1 MB = 1000 KB), lo cual puede llevarnos a confusiones a la hora de alinear correctamente las particiones o de especificar su tamaño. Si no nos aclaramos con esta unidad, podemos introducir el comando "unit s" para medir los tamaños en sectores, o bien "unit MiB" si preferimos medir los tamaños en mebibytes (1 MiB = 1024 KiB).
Ahora, si intentamos mostrar de nuevo la lista de particiones con el comando "p", veremos que en lugar de tener 8590 MB de capacidad en el dispositivo, nos dirá que tenemos 8192 MiB:
1 2 3 4 5 6 7 8 9 |
(parted) unit mib (parted) p Error: /dev/loop0: unrecognised disk label Model: Loopback device (loopback) Disk /dev/loop0: 8192MiB Sector size (logical/physical): 512B/512B Partition Table: unknown Disk Flags: (parted) |
Para una configuración básica, como es crear una única partición FAT32 arrancable desde BIOS que ocupe toda la imagen de nuestro pseudo-pendrive (para que podamos arrancar también sobre UEFI sin necesidad de crear otra partición adicional), tendríamos que escribir los siguientes comandos en parted:
1 2 3 |
(parted) mkt msdos (parted) mkp primary fat32 1 -1s (parted) set 1 boot on |
Por partes:
El disco se quedará con este esquema de particiones:
1 2 3 4 5 6 7 8 9 10 11 |
(parted) p Model: Loopback device (loopback) Disk /dev/loop0: 8192MiB Sector size (logical/physical): 512B/512B Partition Table: msdos Disk Flags: Numero Inicio Fin Tamaño Typo Sistema de ficheros Banderas 1 1,00MiB 8192MiB 8191MiB primary fat32 lba (parted) |
Salimos de ahí con el comando "q", y si volvemos a lanzar cat /proc/partitions, nos daremos cuenta que se ha creado un nuevo bloque de control llamado /dev/loop0p1, que significa literalmente "la primera partición del primer dispositivo de bucle":
1 2 3 4 5 6 7 8 9 10 |
# cat /proc/partitions major minor #blocks name 8 0 488386584 sda 8 1 41943040 sda1 8 2 438052445 sda2 8 3 8387591 sda3 11 0 1048575 sr0 7 0 8388608 loop0 259 0 8387584 loop0p1 |
Si es así, ya podremos darle formato a la partición con mkfs.vfat o mkdosfs (en nuestro caso, porque hemos elegido formatearlo en FAT32). Importante: hay que formatear el bloque /dev/loop0p1, no el bloque /dev/loop0 porque de lo contrario sobrescribiremos la tabla de particiones y luego no podremos instalar el cargador GRUB2 sobre el sector de arranque del MBR.
1 2 |
# mkfs.vfat -F 32 /dev/loop0p1 mkfs.fat 4.1 (2017-01-24) |
Hecho esto, ya podremos montar la(s) particion(es) que hayamos formateado en la imagen. Podemos hacerlo con el comando mount siguiendo la misma sintaxis que mencionó Jinkros en este artículo, o bien con el ayudante udisks2 (sin necesidad de tener permisos de superusuario) de la siguiente forma:
1 2 |
$ udisksctl mount --block-device /dev/loop0p1 Mounted /dev/loop0p1 at /run/media/alex/E247-D5F8. |
Ahora tenemos que saber dónde está montado nuestro "pendrive virtual", que en nuestro caso lo está en /run/media/alex/E247-D5F8, tal y como nos ha dicho udisks2 después de montar el volumen del disco; pues lo necesitaremos para poder instalar GRUB2 sobre él, siguiendo las mismas indicaciones del post ya mencionado:
1 |
# grub-install --target=x86_64-efi --efi-directory=/run/media/alex/E247-D5F8 --boot-directory=/run/media/alex/E247-D5F8/boot --removable --recheck |
1 |
# grub-install --target=i386-pc --boot-directory=/run/media/alex/E247-D5F8/boot --recheck /dev/loop0 |
En este segundo comando sí que tenemos que especificar que el dispositivo donde queremos instalar GRUB2 es en /dev/loop0, pues es aquí donde se encuentra el sector de arranque de la imagen que se sobrescribirá.
Si ha salido todo correctamente, ya podemos seguir con el tutorial de Jinkros para terminar de configurar GRUB2 y de copiar las ISOs que queramos probar en la imagen de disco. Por ejemplo, nuestra configuración de grub es la siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
set imgdevpath="/dev/disk/by-uuid/E247-D5F8" insmod png insmod part_msdos insmod fat insmod ntfs insmod ext2 set root='hd0,msdos1' set timeout=30 set default=0 if loadfont /boot/grub/fonts/unicode.pf2 ; then set gfxmode=640x480 insmod gfxterm terminal_output gfxterm if [ "${grub_platform}" == "efi" ]; then insmod efi_gop insmod efi_uga fi if [ "${grub_platform}" == "pc" ]; then insmod vbe insmod vga fi fi if background_image /boot/grub/fond.png ; then set color_normal=white/black set color_highlight=yellow/dark-gray else set menu_color_normal=yellow/red set menu_color_highlight=white/black fi menuentry "Debian 8.8 MATE Live" { set isofile='/isos/debian-live-8.8.0-i386-mate-desktop.iso' loopback loop $isofile linux (loop)/live/vmlinuz1 boot=live findiso=${isofile} locales=es_ES.UTF-8 config quiet splash echo -e "Cargando - Espere, por favor..." initrd (loop)/live/initrd1.img } |
Asumiendo que:
Ya tenemos la imagen lista. Podemos desmontar la imagen gráficamente desde el entorno de escritorio que estemos usando, con umount o con el ayudante udisks2:
1 |
$ udisksctl unmount --block-device /dev/loop0p1 |
Si queremos realizar modificaciones en la imagen de pendrive que hemos creado, podemos volver a montarla siguiendo los pasos anteriores (usando losetup para crear un dispositivo de bucle y mount o udisks2 para montarlo), o directamente con el comando mount usando las opciones loop para que se asocie a un dispositivo de bucle, y offset para especificar en qué byte de la imagen se encuentra el sistema de archivos a montar. Para esto último, necesitamos saber primero cómo está alineada la partición que queremos montar en la imagen. Lo podemos calcular por ejemplo con fdisk:
1 2 3 4 5 6 7 8 9 10 |
$ fdisk -l imagen_prueba.img Disco imagen_prueba.img: 8 GiB, 8589934592 bytes, 16777216 sectores Unidades: sectores de 1 * 512 = 512 bytes Tamaño de sector (lógico/físico): 512 bytes / 512 bytes Tamaño de E/S (mínimo/óptimo): 512 bytes / 512 bytes Tipo de etiqueta de disco: dos Identificador del disco: 0x2a448436 Disposit. Inicio Comienzo Final Sectores Tamaño Id Tipo imagen_prueba.img1 * 2048 16777215 16775168 8G c W95 FAT32 (LBA) |
Nos tenemos que fijar en cuántos bytes por sector hay en la imagen de disco y en qué sector empieza la partición que queremos montar. Por lo general, como en la mayoría de discos físicos, la imagen cuenta los sectores por 512 bytes, mientras que la partición a montar está alineada en el sector 2048. Por lo tanto, el valor de offset se calculará multiplicando 2048 por 512 a la hora de montarlo en /mnt, que es el lugar que hemos escogido nosotros para montar la partición:
1 |
# mount -o loop,offset $((2048*512)) imagen_prueba.img /mnt |
Y lo desmontaremos con umount como lo haríamos normalmente:
1 |
# umount /mnt |
Antes de iniciar la máquina virtual, necesitamos asegurarnos de que tenemos el virtualizador QEMU instalado, y sus extensiones para otras arquitecturas de computadores. También necesitaremos instalar los módulos de BIOS ovmf (Tianocore UEFI firmware) para poder virtualizar con firmware UEFI en QEMU.
Los comandos de instalación para las distribuciones más conocidas son:
1 |
# apt install qemu qemu-system-x86 ovmf |
1 |
# pacman -S qemu qemu-arch-extra ovmf |
1 2 3 4 |
# dnf install qemu # dnf install dnf-plugins-core # dnf config-manager --add-repo http://www.kraxel.org/repos/firmware.repo # dnf install edk2.git-ovmf-x64 |
1 |
# emerge app-emulation/qemu sys-firmware/edk2-ovmf |
1 |
# zypper in qemu ovmf |
Hecha la instalación, podemos empezar a comprobar si funciona correctamente el arranque de la imagen con el siguiente comando para lanzar el emulador:
1 2 3 4 5 |
$ qemu-system-x86_64 \ -k es \ -vga std \ -m 2048 \ -hda imagen_prueba.img |
Con esto iniciaremos una máquina de 64 bits con arranque BIOS, con el teclado configurado al idioma español (-k es), con gráficos SVGA estándar (-vga std), con 2 GiB de memoria RAM (-m 2048) y usando como disco duro principal la imagen que acabamos de preparar (-hda imagen_prueba.img).
En caso de querer probarlo con firmware UEFI, tendremos que añadir la opción -bios especificando la ruta del firmware UEFI que hemos bajado del paquete ovmf (normalmente se ubica en /usr/share/ovmf/ovmf_code_x64.bin), y la opción -no-kvm para forzar a que QEMU no use KVM, ya que puede dar problemas de compatibilidad con el firmware EFI virtualizado.
1 2 3 4 5 6 7 |
$ qemu-system-x86_64 \ -k es \ -vga std \ -m 2048 \ -hda imagen.img \ -bios /usr/share/ovmf/ovmf_code_x64.bin \ -no-kvm |
Una vez comprobado que arranque correctamente, ya podemos copiar definitivamente los archivos de configuración que hayamos cambiado en el disco virtual de pruebas al pendrive multiboot que ya teníamos configurado. Al principio puede parecer engorroso, pero una vez aprendida la mecánica y a lo mejor haciendo el arreglo con scripts, puede resultar más ágil y práctico probar el arranque de un pendrive multiboot que estemos configurando sin necesidad de reiniciar el equipo.
Por supuesto que este tutorial también puede abarcar otros propósitos y necesidades, eso ya es cuestión también de echarle algo de imaginación 😉