0%

Custom your Linux live CD using linux-live

Recently I was working on a Linux Bare Metal Recovery releated requirement. Users should be allowed to use a customed Linux live CD to start a temporary Linux operating system that won’t make any changes to the storage, and then they can use the build-in tools to perform system recovery, e.g. using sfdisk to restore the volume/partition data and using GRUB toolchains to fix the startup items. Thus, we need to find a way to custom our a Linux recovery live CD firstly.

Due to I’m a newbie in such area, I googled about the Linux live CD and found there’s already some out-of-the-box images like Slax. However, it does not provide the feature customization kit so that does not meet our expectation. I searched linux live kit project from GitHub wishing to found a available solution. The projects are mainly some sorts of shell scripts to pack specified Linux distro into a ISO file.

I found the linux-live project by Tomas-M can pack any Linux system whose kernel supports squashfs , aufs and overlayfs into an live CD ISO image. All I need to do is just clone the project, make some configuration and run a script to produce the target ISO, which is quite simple and flexible. Here’s the steps in detail:

  1. clone the project to a directory that won’t be packed latter, for example /opt.

    $ cd /opt && git clone https://github.com/Tomas-M/linux-live.git
  2. edit the linux-live/config file to filter which directories need to be packed:

    # Kernel file, will be copied to your Live Kit
    # Your kernel must support aufs and squashfs. Debian Jessie's kernel is ready
    # out of the box.
    VMLINUZ=/vmlinuz

    # List of directories for root filesystem
    # No subdirectories are allowed, no slashes,
    # so You can't use /var/tmp here for example
    # Exclude directories like proc sys tmp
    MKMOD="bin etc home lib lib64 libx32 root sbin srv usr var"

    use find /boot | grep vmlinuz to find where the vmlinuz image is and create a /vmlinuz symlink to that path:

    $ find /boot | grep vmlinuz
    /boot/vmlinuz-0-rescue-2348d36f53074ace8b9850051a3d34b5
    /boot/vmlinuz-5.10.0-182.0.0.95.oe2203sp3.x86_64
    $ ln -s /boot/vmlinuz-5.10.0-182.0.0.95.oe2203sp3.x86_64 /vmlinuz
  1. run linux-live/build script, a temporary folder named linux-data-xxx containning data to be packed and two shell scripts that used to produce ISO or ZIP file will be generated to /tmp

    $ tree /tmp
    /tmp
    |-- /linux-data-xxxx
    |-- gen_linux_iso.sh
    |-- gen_linux_zip.sh
  2. run /tmp/gen_linux_iso.sh and an ISO image /tmp/linux-x86_64.iso will be packed.

After the four simple steps are done, I created a new virtual machine in VMWare, attached this ISO image to virtual CD-ROM and chose to boot from CD-ROM. It works! A ugly boot background shows and the systems loads successfully!

live-linux-isolinux

However, several days later, I found that the ISO cannot boot on UEFI mode (the previous vm is booted from BIOS). What’s worse, the linux-live kit cannot build ISO image from aarch64 platform. I checked the build log and found it output error when executing initramfs_create. It fails because the build scripts require some pre-compiled binaries like busybox, eject, mkfs.xfs placed in directory initramfs/static. Thus, this kit won’t work on such case definitely without performing any modification to it! Now, to sum up, this live kit can only produce ISO image which works on x86_64 platform and support BIOS booting only, but our requirement is to build ISO image that works on both x86_64 and aarch64, and can boot from both UEFI and BIOS. I think, the binaries platform issue can solved by recompiling them on aarch64 platform, but the UEFI booting issue is really hard for me since I have no idea about how Linux boots from ISO!

I have written a blog How Linux Boots introducing how Linux boot from disk, but this time things got a little different as we are trying to boot from ISO image, or say, a optical disk.

ISOLINUX & GRUB

// isolinux is designed for ISO boot specificly
qemu-euler-aarch64-install
virt-manager-create-new-instance
live-linux-grub

ISO9660 & El Torito

// iso8660 standard and El Torito boot

Build x86_64 EFI ISO image

// replace ESP image and El torito boot entry

// build hybrid CD

I downloaded a openEuler-22.03-LTS-SP3-x86_64-dvd.iso installation ISO file and mounted it as iso9660 filesystem to check it’s structure as followed:

$ mkdir /mnt/sr0
$ mount -t iso9660 -o loop,ro openEuler-22.03-LTS-SP3-x86_64-dvd.iso /mnt/sr0
$ tree /mnt/sr0
/mnt/sr0/
├── EFI
│   ├── BOOT
│   │   ├── BOOTX64.EFI
│   │   ├── fonts
│   │   │   ├── TRANS.TBL
│   │   │   └── unicode.pf2
│   │   ├── grub.cfg
│   │   ├── grubx64.efi
│   │   ├── mmx64.efi
│   │   └── TRANS.TBL
│   └── TRANS.TBL
├── images
│   ├── efiboot.img
│   ├── install.img
│   ├── pxeboot
│   │   ├── initrd.img
│   │   ├── TRANS.TBL
│   │   └── vmlinuz
│   └── TRANS.TBL
├── isolinux
│   ├── boot.cat
│   ├── boot.msg
│   ├── grub.conf
│   ├── initrd.img
│   ├── isolinux.bin
│   ├── isolinux.cfg
│   ├── ldlinux.c32
│   ├── libcom32.c32
│   ├── libutil.c32
│   ├── splash.png
│   ├── TRANS.TBL
│   ├── vesamenu.c32
│   └── vmlinuz
├── Packages
├── Repodata
├── docs

We can see except the structure is rather clear:

  • /isolinux should contains the ISOLINUX bootloader used to enable this ISO image to boot on BIOS mode.
  • /Packages packed many rpm package that may be used during the installtion.
  • /EFI contains a hierarchy that look like a ESP partition.
  • /images contains some disk images.

It’s notable that and there’s a also image file named efiboot.img placed in /image. By using the file command to check the format of efiboot.img, we can discover it’s a image with vfat format.

$ file /mnt/sr0/images/efiboot.img 
/mnt/sr0/images/efiboot.img: DOS/MBR boot sector, code offset 0x3c+2, OEM-ID "mkfs.fat", sectors/cluster 4, reserved sectors 4, root entries 512, sectors 14336 (volumes <=32 MB), Media descriptor 0xf8, sectors/FAT 12, sectors/track 32, serial number 0xeaba75ff, label: "ANACONDA ", FAT (12 bit)

Also, I mounted it as a loop device to check what files it contains and got that it nearly process the similar file of /EFI.

$ mkdir /mnt/efiboot
$ mount -o ro,loop /mnt/sr0/images/efiboot.img /mnt/efiboot
$ tree /mnt/efiboot
/mnt/efiboot
└── EFI
└── BOOT
├── BOOTX64.EFI
├── fonts
│   └── unicode.pf2
├── grub.cfg
├── grubx64.efi
└── mmx64.efi

4 directories, 5 files
$ tree /mnt/sr0/EFI
/mnt/sr0/EFI
├── BOOT
│   ├── BOOTX64.EFI
│   ├── fonts
│   │   ├── TRANS.TBL
│   │   └── unicode.pf2
│   ├── grub.cfg
│   ├── grubx64.efi
│   ├── mmx64.efi
│   └── TRANS.TBL
└── TRANS.TBL

3 directories, 8 files

So I compared the md5 checksum of these files and find the checksum all matched, this confirmed my hypothesis: the efiboot.img is simply a vfat image packed /EFI. I think the efiboot.img is used to simulate a ESP partition used for UEFI booting.

$ find /mnt/sr0/EFI -type f | grep -v TBL | sort | xargs -i md5sum {}
bb78df58c62474ec8596fe0bfd89bab6 /mnt/sr0/EFI/BOOT/BOOTX64.EFI
c6a4d48acacf45900fe9c195ed87e13f /mnt/sr0/EFI/BOOT/fonts/unicode.pf2
50d210676c58844931553b79ad67870e /mnt/sr0/EFI/BOOT/grub.cfg
770f6e3f7985f749b26d8fbc306361a7 /mnt/sr0/EFI/BOOT/grubx64.efi
fa13822c2cea99f99b64d81b8ed51918 /mnt/sr0/EFI/BOOT/mmx64.efi
$ find /mnt/efiboot -type f | sort | xargs -i md5sum {}
bb78df58c62474ec8596fe0bfd89bab6 /mnt/efiboot/EFI/BOOT/BOOTX64.EFI
c6a4d48acacf45900fe9c195ed87e13f /mnt/efiboot/EFI/BOOT/fonts/unicode.pf2
50d210676c58844931553b79ad67870e /mnt/efiboot/EFI/BOOT/grub.cfg
770f6e3f7985f749b26d8fbc306361a7 /mnt/efiboot/EFI/BOOT/grubx64.efi
fa13822c2cea99f99b64d81b8ed51918 /mnt/efiboot/EFI/BOOT/mmx64.efi

So I copied the efiboot.img from the OpenEuler installation ISO to /tmp/linux-data-xxx/linux/boot in order to boot from this image. Then changed the boot entry from linux/boot/isolinux.bin to linux/boot/efiboot.img and packed the ISO image and reboot from it on UEFI mode again, the GRUB shell prompted succesfully!

cd /tmp/linux-data-xxx && mkisofs -o "/tmp/linux-x86_64-efi.iso"
-v -J -R -D -N --hide-rr-moved -A linux -V linux \
-no-emul-boot -boot-info-table -boot-load-size 4 \
-b linux/boot/efiboot.img -c linux/boot/isolinux.boot .

This GRUB bootloader load the grub.cfg from /EFI/BOOT/grub.cfg so I created a directory /EFI/BOOT in the /tmp/linux-data-xxx and configure the grub.cfg in it as follows:
set default="1"

function load_video {
insmod efi_gop
insmod efi_uga
insmod video_bochs
insmod video_cirrus
insmod all_video
}

load_video
set gfxpayload=keep
insmod gzio
insmod part_gpt
insmod ext2

set timeout=60
### END /etc/grub.d/00_header ###

search --no-floppy --set=root -l 'linux'

### BEGIN /etc/grub.d/10_linux ###
menuentry 'Run Linux Live' --class openEuler --class gnu-linux --class gnu --class os {
linuxefi /linux/boot/vmlinuz load_ramdisk=1 prompt_ramdisk=0 rw printk.time=0 apparmor=0
initrdefi /linux/boot/initrfs.img
}
menuentry 'Run Linux Live debug' --class openEuler --class gnu-linux --class gnu --class os {
linuxefi /linux/boot/vmlinuz load_ramdisk=1 prompt_ramdisk=0 rw printk.time=0 apparmor=0 debug
initrdefi /linux/boot/initrfs.img
}

This time GRUB will use kernel in /linux/boot/vmlinuz and the initrd in /linux/boot/initrfs.img to boot the Linux in UEFI mode. The hierarchy structure of the directory to be packed shoud be like:
$ tree /tmp/linux-data-27494      
/tmp/linux-data-27494
├── EFI
│   └── BOOT
│      └── grub.cfg
└── linux
├── boot
│   ├── bootinst.bat
│   ├── bootinst.sh
│   ├── bootlogo.png
│   ├── efiboot.img
│   ├── extlinux.x32
│   ├── extlinux.x64
│   ├── initrfs.img
│   ├── isolinux.bin
│   ├── ldlinux.c32
│   ├── libcom32.c32
│   ├── libutil.c32
│   ├── mbr.bin
│   ├── pxelinux.0
│   ├── syslinux.cfg
│   ├── syslinux.com
│   ├── syslinux.exe
│   └── vesamenu.c32
├── changes
└── modules
└── 01-core.sb

I repacked the ISO and reboot it, this time it works fine! However, this ISO image can only boot in UEFI mode while the origin one can can boot in BIOS mode. It will be much more convenient for users to be able to boot the linux live system in both BIOS and UEFI mode just using one ISO image, and almost all Linux distribution installation media can do that, so it’s fessible to make one such ‘hybrid’ CD. By reading the document of mkisofs I found that El Torito extention support multiple boot entry in one CD. By adding the -eltorito-alt-boot -no-emul-boot -e linux/boot/efiboot.img option we can create the hybrid CD that works in dual mode:
cd /tmp/linux-data-xxx && mkisofs -o "/tmp/linux-x86_64-efi.iso"
-v -J -R -D -N --hide-rr-moved -A linux -V linux \
-no-emul-boot -boot-info-table -boot-load-size 4 \
-b linux/boot/efiboot.img -c linux/boot/isolinux.boot \
-eltorito-alt-boot -no-emul-boot -e linux/boot/efiboot.img .

Now, if you boot it in BIOS mode, it will use ISOLINUX as the bootloader, and if you choose to boot in UEFI, GRUB menu will show.

Support aarch64

A few days latter, I got the new requirement to custom Linux liveCD of aarch64 platform. All the operation above is performed on the platform of x86_64, It’s now known if it still works on aarch64. I installed an aarch64 OpenEuler enviroment and replay these procedures, the bad news is that the build script failed at initramfs_create call, the ‘exec format error’ prints when executing busybox in linux-live/initramfs/static. I checked the files in linux-live/initramfs/static and found all those precompile binaries provided by this linux-live kit like busybox are statically linked executable in format of ELF Intel 80386, which cannot be used on aarch64.

$ find initramfs/static | xargs file
initramfs/static: directory
initramfs/static/blkid: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, no section header
initramfs/static/mount.dynfilefs: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, no section header
initramfs/static/mkfs.xfs.custom: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, no section header
initramfs/static/xfs_growfs: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, no section header
initramfs/static/mount.httpfs2: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, no section header
initramfs/static/busybox: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, stripped
initramfs/static/ncurses-menu: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, no section header
initramfs/static/mc: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, no section header
initramfs/static/eject: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, no section header

Some source of the executable like busybox and blkid can be found on Github while the others remain unknown. Thus, I need to delve into the source of this project to find out how this kit works, and recompile these binaries.

First, download busybox source code and build a statically linked busybox executable to replace the old one in linux-live/initramfs/static. If your want to use a cross compile toolchain to finish this step, you can refer to this tutorial : compiling busybox simple. You can also make it directly on a aarch64 environment.

After I read the initramfs_create script, I found the busybox is used to simulate the Linux common utils, these utils like /bin/ls, /bin/bash are all linked to /bin/busybox. I also noticed that the busybox can simulate blkid and eject too, and the remaining executables like xfs_growfs are not necessary so recompile busybox on aarch64 is enough. After replaced the busybox executable. I restart the build script and the ‘exec format error’ didn’t show anymore.

I use the newly produced aarch64 ISO to boot and nothing is bootable. It’s not suprising since there’s alse other platform-dependent binaries used in the kit that I haven’t replaced yet. It’s easy to think of: the bootloader. Since ARM archtecture doesn’t have BIOS boot, we can discard the ISOLINUX bootloader and use GRUB only. Just like how I did to add support to x86_64 UEFI mode before, I downloaded a openEuler-22.03-LTS-SP3-aarch64-dvd.iso and extract the efiboot.img to replace the previous one. Also, I modified the /EFI/BOOT/grub.cfg to fit the aarch64 platform as below:

set default="1"

function load_video {
if [ x$feature_all_video_module = xy ]; then
insmod all_video
else
insmod efi_gop
insmod efi_uga
insmod ieee1275_fb
insmod vbe
insmod vga
insmod video_bochs
insmod video_cirrus
fi
}

load_video
set gfxpayload=keep
insmod gzio
insmod part_gpt
insmod ext2

set timeout=60
### END /etc/grub.d/00_header ###

search --no-floppy --set=root -l 'linux'

### BEGIN /etc/grub.d/10_linux ###
menuentry 'Run Linux Live' --class openEuler --class gnu-linux --class gnu --class os {
linux /linux/boot/vmlinuz load_ramdisk=1 prompt_ramdisk=0 rw printk.time=1 apparmor=0 video=VGA-1:640x480-32@60me rgbh console=tty0
initrd /linux/boot/initrfs.img
}
menuentry 'Run Linux Live debug' --class openEuler --class gnu-linux --class gnu --class os {
linux /linux/boot/vmlinuz load_ramdisk=1 prompt_ramdisk=0 rw printk.time=1 apparmor=0 debug video=VGA-1:640x480-32@60me rgbh console=tty0
initrd /linux/boot/initrfs.img
}

After these modification is done, I repacked the ISO and boot again, the GRUB menu shows! However, no display come to screen anymore. We have successfully load the bootloader, it seem that the kernel or initramfs have some problem durring the early boot stage. To check where got stuck, I need to use qemu to debug the early boot process.

If your are using ArchLinux, you just use sudo pacman -S qemu-full qemu-system-aarch64 virt-manager dnsmasq to install qemu and virt-manager. virt-manager is a nice user graphic interface for qemu that function like VMware to allow aarch64 operating system run on my x86_64 PC.

You can also build qemu from source:

  1. build ninja

    $ wget https://github.com/ninja-build/ninja/archive/refs/tags/v1.10.2.tar.gz
    $ tar zxvf v1.10.2.tar.gz && cd ninja-1.10.2
    $ ./configure.py --bootstrap
    $ sudo cp ninja /usr/bin
  2. build qemu

    $ mkdir build && cd build
    $ sudo mkdir -p /usr/local/qemu
    $ ../configure --enable-virtfs --target-list=aarch64-softmmu,arm-softmmu --prefix=/usr/local/qemu
    $ make -j16
    $ sudo make install
  3. add /usr/local/qemu/bin to $PATH

If you encountered the error “Failed to connect socket to ‘/var/run/libvirt/libvirt-sock’”, check if libvertd service is started.

configure the qemu network, create a network card on host:

$ sudo ip tuntap add dev tap0 mode tap
$ sudo ip link set dev tap0 up
$ sudo ip address add dev tap0 192.168.2.128/24
$ ifconfig
tap0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 192.168.2.128 netmask 255.255.255.0 broadcast 0.0.0.0
ether 8a:3c:1a:1f:cc:6d txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

$ qemu-system-aarch64 -m 2000 -cpu cortex-a72 -smp 8 -M virt \
-drive if=none,file=/home/xuranus/workspace/linux-live/tmp/linux-aarch64-efi.iso,id=cdrom,media=cdrom \
-device virtio-scsi-device \
-device scsi-cd,drive=cdrom \
-net nic -net user \
-kernel /home/xuranus/workspace/linux-live/tmp/vmlinuz \
-initrd /home/xuranus/workspace/linux-live/tmp/linux-initramfs-29628.img \
-append "earlyprintk=serial,ttyAMA0 console=ttyAMA0" -nographic
Disqus评论区没有正常加载,请使用科学上网