@@ -85,7 +85,7 @@ check_rust() { has_cmd rustc && rustup target list 2>/dev/null | grep -q "
8585check_ovmf () { get_ovmf_path & > /dev/null; }
8686check_bootloader () { [[ -f " ${ESP_DIR} /EFI/BOOT/BOOTX64.EFI" ]]; }
8787check_qemu () { has_cmd qemu-system-x86_64; }
88- check_disk_tools () { has_cmd qemu-img && has_cmd parted && has_cmd mkfs.vfat && has_cmd mkfs.ext4; }
88+ check_disk_tools () { has_cmd qemu-img && has_cmd parted && has_cmd mkfs.vfat && has_cmd mkfs.ext4 && has_cmd mcopy ; }
8989# Distribution checks removed - network downloader handles ISO acquisition
9090check_disk_50g () { [[ -f " ${TESTING_DIR} /test-disk-50g.img" ]]; }
9191
@@ -113,7 +113,7 @@ cmd_status() {
113113 print_check " $( has_cmd nasm && echo 1 || echo 0) " " NASM Assembler"
114114 print_check " $( check_qemu && echo 1 || echo 0) " " QEMU" " $( qemu-system-x86_64 --version 2> /dev/null | head -1 | grep -oP ' \d+\.\d+\.\d+' || echo ' missing' ) "
115115 print_check " $( check_ovmf && echo 1 || echo 0) " " OVMF Firmware" " $( get_ovmf_path 2> /dev/null || echo ' missing' ) "
116- print_check " $( check_disk_tools && echo 1 || echo 0) " " Disk Tools" " parted, mkfs.vfat, mkfs.ext4"
116+ print_check " $( check_disk_tools && echo 1 || echo 0) " " Disk Tools" " parted, mkfs.vfat, mkfs.ext4, mtools "
117117
118118 printf " \n${C_DIM} ── Build ──${C_RESET} \n"
119119 print_check " $( check_bootloader && echo 1 || echo 0) " " Bootloader (BOOTX64.EFI)"
@@ -133,29 +133,29 @@ do_install_packages() {
133133
134134 case " ${distro} " in
135135 arch|manjaro|endeavouros)
136- pkgs=(base-devel nasm qemu-full ovmf parted dosfstools e2fsprogs util-linux rsync curl wget squashfs-tools cdrtools)
136+ pkgs=(base-devel nasm qemu-full ovmf parted dosfstools e2fsprogs util-linux rsync curl wget squashfs-tools cdrtools mtools )
137137 install_cmd=" sudo pacman -S --needed --noconfirm"
138138 ;;
139139 debian|ubuntu|pop|linuxmint|kali)
140- pkgs=(build-essential nasm qemu-system-x86 ovmf parted dosfstools e2fsprogs util-linux rsync curl wget squashfs-tools genisoimage qemu-utils)
140+ pkgs=(build-essential nasm qemu-system-x86 ovmf parted dosfstools e2fsprogs util-linux rsync curl wget squashfs-tools genisoimage qemu-utils mtools )
141141 install_cmd=" sudo apt-get install -y -qq"
142142 log_info " Updating package lists..."
143143 sudo apt-get update -qq
144144 ;;
145145 fedora)
146- pkgs=(gcc make nasm qemu-system-x86 edk2-ovmf parted dosfstools e2fsprogs util-linux rsync curl wget squashfs-tools genisoimage qemu-img)
146+ pkgs=(gcc make nasm qemu-system-x86 edk2-ovmf parted dosfstools e2fsprogs util-linux rsync curl wget squashfs-tools genisoimage qemu-img mtools )
147147 install_cmd=" sudo dnf install -y -q"
148148 ;;
149149 rhel|centos|almalinux|rocky)
150- pkgs=(gcc make nasm qemu-kvm edk2-ovmf parted dosfstools e2fsprogs util-linux rsync curl wget squashfs-tools genisoimage)
150+ pkgs=(gcc make nasm qemu-kvm edk2-ovmf parted dosfstools e2fsprogs util-linux rsync curl wget squashfs-tools genisoimage mtools )
151151 install_cmd=" sudo yum install -y -q"
152152 ;;
153153 opensuse* |suse)
154- pkgs=(gcc make nasm qemu-x86 qemu-ovmf-x86_64 parted dosfstools e2fsprogs util-linux rsync curl wget squashfs genisoimage)
154+ pkgs=(gcc make nasm qemu-x86 qemu-ovmf-x86_64 parted dosfstools e2fsprogs util-linux rsync curl wget squashfs genisoimage mtools )
155155 install_cmd=" sudo zypper install -y"
156156 ;;
157157 alpine)
158- pkgs=(build-base nasm qemu-system-x86_64 ovmf parted dosfstools e2fsprogs util-linux rsync curl wget squashfs-tools cdrkit bash)
158+ pkgs=(build-base nasm qemu-system-x86_64 ovmf parted dosfstools e2fsprogs util-linux rsync curl wget squashfs-tools cdrkit bash mtools )
159159 install_cmd=" sudo apk add"
160160 ;;
161161 * )
@@ -374,6 +374,83 @@ do_create_disk() {
374374 log_success " Test disk ready (50GB sparse, will be formatted by inject)"
375375}
376376
377+ populate_esp_image_with_mtools () {
378+ local src_dir=" $1 "
379+ local esp_img=" $2 "
380+ local -a entries=()
381+
382+ shopt -s dotglob nullglob
383+ entries=(" ${src_dir} " /* )
384+ shopt -u dotglob nullglob
385+
386+ [[ ${# entries[@]} -gt 0 ]] || return 0
387+
388+ mmd -i " $esp_img " ::/EFI > /dev/null 2>&1 || true
389+ mmd -i " $esp_img " ::/EFI/BOOT > /dev/null 2>&1 || true
390+ mcopy -i " $esp_img " -s " ${entries[@]} " ::/ > /dev/null 2>&1
391+ }
392+
393+ populate_esp_image_with_loop_mount () {
394+ local src_dir=" $1 "
395+ local esp_img=" $2 "
396+ local mnt
397+ local loop_dev=" "
398+ local attached=0
399+ local mounted=0
400+
401+ mnt=$( mktemp -d)
402+
403+ if has_cmd modprobe; then
404+ sudo modprobe loop > /dev/null 2>&1 || true
405+ fi
406+
407+ if ! loop_dev=$( sudo losetup --find --show " $esp_img " 2> /dev/null) ; then
408+ rmdir " $mnt "
409+ return 1
410+ fi
411+ attached=1
412+
413+ if ! sudo mount -t vfat " $loop_dev " " $mnt " ; then
414+ sudo losetup -d " $loop_dev " > /dev/null 2>&1 || true
415+ rmdir " $mnt "
416+ return 1
417+ fi
418+ mounted=1
419+
420+ if ! sudo rsync -a " ${src_dir} /" " $mnt /" ; then
421+ (( mounted == 1 )) && sudo umount " $mnt " > /dev/null 2>&1 || true
422+ (( attached == 1 )) && sudo losetup -d " $loop_dev " > /dev/null 2>&1 || true
423+ rmdir " $mnt "
424+ return 1
425+ fi
426+
427+ sudo umount " $mnt "
428+ sudo losetup -d " $loop_dev "
429+ rmdir " $mnt "
430+ }
431+
432+ refresh_esp_image () {
433+ local esp_img=" $1 "
434+ local src_dir=" $2 "
435+ local esp_size
436+
437+ esp_size=$( du -sb " ${src_dir} " | awk ' {print int(($1 / 1024 / 1024) + 50)}' )
438+ dd if=/dev/zero of=" $esp_img " bs=1M count=" $esp_size " status=none 2> /dev/null
439+ mkfs.vfat -F 32 -n ESP " $esp_img " > /dev/null 2>&1
440+
441+ if has_cmd mcopy && has_cmd mmd; then
442+ if populate_esp_image_with_mtools " $src_dir " " $esp_img " ; then
443+ return 0
444+ fi
445+ log_warn " mtools copy failed; falling back to loop-mount copy"
446+ else
447+ log_info " mtools not found; using loop-mount copy for ESP image"
448+ fi
449+
450+ populate_esp_image_with_loop_mount " $src_dir " " $esp_img " \
451+ || die " Failed to populate ESP image. Install mtools or ensure loop devices are available."
452+ }
453+
377454do_launch_qemu () {
378455 log_step " Launching QEMU"
379456
@@ -394,16 +471,7 @@ do_launch_qemu() {
394471 local esp_img=" ${TESTING_DIR} /esp-temp.img"
395472 if [[ ! -f " $esp_img " ]] || [[ " ${ESP_DIR} /EFI/BOOT/BOOTX64.EFI" -nt " $esp_img " ]] || [[ " ${FORCE_MODE} " == " true" ]]; then
396473 log_info " Refreshing boot ESP image..."
397- local esp_size
398- esp_size=$( du -sb " ${ESP_DIR} " | awk ' {print int(($1 / 1024 / 1024) + 50)}' )
399- dd if=/dev/zero of=" $esp_img " bs=1M count=" $esp_size " status=none 2> /dev/null
400- mkfs.vfat -F 32 -n ESP " $esp_img " > /dev/null 2>&1
401- local mnt
402- mnt=$( mktemp -d)
403- sudo mount -o loop " $esp_img " " $mnt "
404- sudo rsync -a " ${ESP_DIR} /" " $mnt /" 2> /dev/null || true
405- sudo umount " $mnt "
406- rmdir " $mnt "
474+ refresh_esp_image " $esp_img " " $ESP_DIR "
407475 fi
408476 # Disk 0 (virtio): ESP FAT32 image — UEFI boots BOOTX64.EFI from here
409477 # Disk 1 (virtio): raw HelixFS image — kernel mounts this as data storage
@@ -431,14 +499,7 @@ do_launch_qemu() {
431499 local esp_img=" ${TESTING_DIR} /esp-temp.img"
432500 if [[ ! -f " $esp_img " ]] || [[ " ${ESP_DIR} " -nt " $esp_img " ]]; then
433501 log_info " Creating ESP disk image..."
434- local esp_size=$( du -sb " ${ESP_DIR} " | awk ' {print int(($1 / 1024 / 1024) + 50)}' )
435- dd if=/dev/zero of=" $esp_img " bs=1M count=$esp_size status=none 2> /dev/null || true
436- mkfs.vfat -F 32 -n ESP " $esp_img " > /dev/null 2>&1 || true
437- local mnt=$( mktemp -d)
438- sudo mount -o loop " $esp_img " " $mnt "
439- sudo rsync -a " ${ESP_DIR} /" " $mnt /" 2> /dev/null || true
440- sudo umount " $mnt "
441- rmdir " $mnt "
502+ refresh_esp_image " $esp_img " " $ESP_DIR "
442503 fi
443504 qemu-system-x86_64 \
444505 -enable-kvm \
@@ -662,14 +723,7 @@ do_launch_thinkpad() {
662723 local esp_img=" ${TESTING_DIR} /esp-temp.img"
663724 if [[ ! -f " $esp_img " ]] || [[ " ${ESP_DIR} " -nt " $esp_img " ]]; then
664725 log_info " Creating ESP disk image..."
665- local esp_size=$( du -sb " ${ESP_DIR} " | awk ' {print int(($1 / 1024 / 1024) + 50)}' )
666- dd if=/dev/zero of=" $esp_img " bs=1M count=$esp_size status=none 2> /dev/null || true
667- mkfs.vfat -F 32 -n ESP " $esp_img " > /dev/null 2>&1 || true
668- local mnt=$( mktemp -d)
669- sudo mount -o loop " $esp_img " " $mnt "
670- sudo rsync -a " ${ESP_DIR} /" " $mnt /" 2> /dev/null || true
671- sudo umount " $mnt "
672- rmdir " $mnt "
726+ refresh_esp_image " $esp_img " " $ESP_DIR "
673727 fi
674728 qemu-system-x86_64 \
675729 -enable-kvm \
0 commit comments