From patchwork Sat Apr 18 00:40:03 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2226 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1776472854; bh=+WyhX6hS7KMZR4GSSmTb6DQzDJ8tqkvLJNW4re31FGU=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=e2UJNKoPEehElp0ljyIFEepeg9Bxe8i1ZJDivpUU9LOhGVRRBF7pnkikuCgXqZ2cW q9kXv6AGVD2fwYhlMh7riOB5j786dCzZQLcnwu8FMfU9Ce0SCAR3eHDKzctj3s9eAz 4YhQSqvdr71Wnz8sIPVQR6MWUOTXQZsPce3IKcMw= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id DDD996A516 for ; Fri, 17 Apr 2026 18:40:54 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id rUituNSoX63y for ; Fri, 17 Apr 2026 18:40:54 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1776472854; bh=+WyhX6hS7KMZR4GSSmTb6DQzDJ8tqkvLJNW4re31FGU=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=e2UJNKoPEehElp0ljyIFEepeg9Bxe8i1ZJDivpUU9LOhGVRRBF7pnkikuCgXqZ2cW q9kXv6AGVD2fwYhlMh7riOB5j786dCzZQLcnwu8FMfU9Ce0SCAR3eHDKzctj3s9eAz 4YhQSqvdr71Wnz8sIPVQR6MWUOTXQZsPce3IKcMw= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id CB62D6A539 for ; Fri, 17 Apr 2026 18:40:54 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1776472852; bh=b7BXrbTadpEGPnrZkGVYFw5s4q/wnkc6uGrJG1ljSQU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=X+Gyw+fNNap6+0JFGor5JVG3vW7wzsZSLC4aFuZlsan7fQwDlf3i5IxCwWAYZsAVh LohH5nrF2yJWrjbO/eyWaUaUU/a4g5vYyVnHTwhz7etsWESoeFRqf1BsKDyt7H7viM YSeKz0rB/zDgGaBmwD035pyYlo0KTJDVhOfAdu5w= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id BB6376A539; Fri, 17 Apr 2026 18:40:52 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id cCc_5Y-A5q5W; Fri, 17 Apr 2026 18:40:52 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1776472846; bh=sOehOekCX0IoeEaj3bwoGpRwKCAo6iIZ4Hb0m0I4zGU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=BlKh7VXrqq53JX+W/yvrjXFzvriESEaZcYg2tf/JH/0lfK80YGoi2YhCBihy/pzKK b1kXP8/CYpK0b+7LODvlvD4v+Rfr2fmSjKaqSzHYJwWDHsKDGiccVHQ397af9AjfNL P32wCMRLCLYiiMmOdqhKrwSUO1b6tKxmb7AAISbc= Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 4787A6A516; Fri, 17 Apr 2026 18:40:46 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Fri, 17 Apr 2026 18:40:03 -0600 Message-ID: <20260418004014.1889749-10-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260418004014.1889749-1-sjg@u-boot.org> References: <20260418004014.1889749-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: RWW3T6ZDCNXM7JFDZD2E5MYSSDFHLV4C X-Message-ID-Hash: RWW3T6ZDCNXM7JFDZD2E5MYSSDFHLV4C X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 09/10] scripts: Add a script to modify an Ubuntu ISO for BLS List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass Rewrite an Ubuntu live ISO so that its appended EFI system partition boots U-Boot instead of shim/grub. The script extracts the casper kernel and initrd from the input ISO, builds a fresh FAT32 ESP with u-boot-app.efi, a matching /loader/entry.conf and the kernel and initrd copied across, then uses xorriso to stream the original ISO to a new one with the ESP replaced in place. Every other boot record (BIOS El Torito, grub2 MBR, GPT layout) is preserved verbatim, and the ISO9660 tree is not touched, so casper still finds its squashfs by disc label at runtime. Intended to be used with the efi-x86_app64 target, which now enables CONFIG_BOOTMETH_BLS=y, CONFIG_FS_ISOFS=y and CONFIG_JOLIET=y by default. Signed-off-by: Simon Glass --- scripts/ubuntu-iso-to-uboot.py | 250 +++++++++++++++++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100755 scripts/ubuntu-iso-to-uboot.py diff --git a/scripts/ubuntu-iso-to-uboot.py b/scripts/ubuntu-iso-to-uboot.py new file mode 100755 index 00000000000..ec32ac7b0dd --- /dev/null +++ b/scripts/ubuntu-iso-to-uboot.py @@ -0,0 +1,250 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0+ +"""Turn an Ubuntu live ISO into one that boots via U-Boot + BLS. + +BLS (Boot Loader Specification) is a freedesktop.org standard for +describing boot menu entries as small files under /loader/entries/ on +the EFI system partition. Each entry names a kernel, initrd and command +line; U-Boot's BOOTMETH_BLS scans the ESP and presents them in its boot +menu, replacing the shim/grub chain that Ubuntu ships by default. + +The ISO's appended EFI System Partition is replaced with a fresh ESP +containing: + + /EFI/BOOT/BOOTX64.EFI U-Boot EFI app (built with CONFIG_BOOTMETH_BLS=y) + /casper/vmlinuz copied from the input ISO + /casper/initrd copied from the input ISO + /loader/entry.conf BLS Type #1 entry pointing at the above + +The ISO9660 tree is preserved unchanged, so casper still finds its squashfs +by disc label at runtime. All other boot records (BIOS El Torito, grub2 MBR, +GPT layout) are preserved by xorriso's -boot_image any replay. + +Quick start (run from the root of the U-Boot tree):: + + # 1. Install host tools + sudo apt install xorriso mtools dosfstools qemu-system-x86 ovmf + + # 2. Download an Ubuntu live ISO (desktop or server both work) + curl -LO https://releases.ubuntu.com/24.04.1/ubuntu-24.04.1-desktop-amd64.iso + + # 3. Build U-Boot as an x86_64 EFI application. The defconfig enables + # BOOTMETH_BLS, FS_ISOFS and JOLIET. If rustc is not installed, + # also pass -d RUST_EXAMPLES -d EXAMPLES to scripts/config and + # re-run olddefconfig before building. + make O=/tmp/b/efi-x86_app64 efi-x86_app64_defconfig + make O=/tmp/b/efi-x86_app64 -j$(nproc) + # produces /tmp/b/efi-x86_app64/u-boot-app.efi + + # 4. Rewrite the ISO to boot via U-Boot + scripts/ubuntu-iso-to-uboot.py ubuntu-24.04.1-desktop-amd64.iso \\ + -u /tmp/b/efi-x86_app64/u-boot-app.efi \\ + -o ubuntu-uboot.iso + + # 5. Try it under QEMU + OVMF + cp /usr/share/OVMF/OVMF_VARS_4M.fd /tmp/OVMF_VARS.fd + qemu-system-x86_64 -machine q35 -m 4096 -smp 2 \\ + -drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \\ + -drive if=pflash,format=raw,file=/tmp/OVMF_VARS.fd \\ + -drive if=virtio,file=ubuntu-uboot.iso,format=raw,readonly=on + +Assumptions: + - Ubuntu-style live ISO with casper/vmlinuz and casper/initrd. + - Input ISO has an appended GPT EFI System Partition + (-append_partition 2 in xorriso's report). + - U-Boot EFI app is built with CONFIG_BOOTMETH_BLS=y, + CONFIG_CMD_ZBOOT=y and FAT support (efi-x86_app64_defconfig is + the reference config). + - xorriso, mtools and dosfstools are installed on the host +""" + +from __future__ import annotations + +import argparse +import os +import re +import shutil +import sys +import tempfile +from pathlib import Path + +# Add the tools directory to the path for u_boot_pylib +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'tools')) + +# pylint: disable=wrong-import-position,import-error +from u_boot_pylib import command +from u_boot_pylib import tout + +DEFAULT_CMDLINE = 'console=ttyS0,115200 console=tty0 --- quiet' +REQUIRED_TOOLS = ('xorriso', 'mcopy', 'mmd', 'mkfs.vfat') +MIB = 1024 * 1024 + + +def check_tools() -> None: + missing = [t for t in REQUIRED_TOOLS if not shutil.which(t)] + if missing: + tout.fatal( + f'missing tools: {" ".join(missing)}\n' + f'try: apt install xorriso mtools dosfstools' + ) + + +def parse_boot_report(iso: Path) -> tuple[str, str]: + """Return (volume_label, esp_partition_guid) from xorriso's mkisofs report. + + Raises SystemExit if the ISO does not have an appended EFI System + Partition on slot 2 + """ + # xorriso prints the report on stdout; stderr carries progress/status. + report = command.output( + 'xorriso', '-indev', str(iso), '-report_el_torito', 'as_mkisofs', + ) + + m_vol = re.search(r"^-V '([^']*)'", report, re.MULTILINE) + m_esp = re.search( + r'^-append_partition 2 ([0-9a-fA-F]+) ', report, re.MULTILINE, + ) + if not m_vol: + tout.fatal('could not find volume label in xorriso report') + if not m_esp: + tout.fatal( + 'could not find appended partition 2 in xorriso report ' + '(is this an Ubuntu-style hybrid ISO?)' + ) + return m_vol.group(1), m_esp.group(1) + + +def extract_from_iso(iso: Path, src: str, dst: Path) -> None: + """Pull a single file out of the ISO9660 tree via xorriso -osirrox""" + dst.parent.mkdir(parents=True, exist_ok=True) + command.run( + 'xorriso', '-osirrox', 'on', '-indev', str(iso), + '-extract', f'/{src.lstrip("/")}', str(dst), + ) + + +def build_esp( + esp_path: Path, + size_mib: int, + uboot_efi: Path, + vmlinuz: Path, + initrd: Path, + entry_conf: str, +) -> None: + """Create a fresh FAT32 ESP at esp_path populated with BLS + kernel. + + Args: + esp_path (Path): Output file to hold the new ESP image + size_mib (int): Size of the ESP in mebibytes + uboot_efi (Path): U-Boot EFI app to install as /EFI/BOOT/BOOTX64.EFI + vmlinuz (Path): Kernel image to install under /casper/vmlinuz + initrd (Path): Initrd image to install under /casper/initrd + entry_conf (str): Contents for /loader/entry.conf (BLS Type #1) + """ + with esp_path.open('wb') as f: + f.truncate(size_mib * MIB) + command.output('mkfs.vfat', '-F32', '-n', 'ESP', str(esp_path)) + command.run('mmd', '-i', str(esp_path), + '::EFI', '::EFI/BOOT', '::loader', '::casper') + + def put(src: Path, dst: str) -> None: + command.run('mcopy', '-i', str(esp_path), str(src), f'::{dst}') + + put(uboot_efi, 'EFI/BOOT/BOOTX64.EFI') + put(vmlinuz, 'casper/vmlinuz') + put(initrd, 'casper/initrd') + + entry = Path(esp_path.parent) / 'entry.conf' + entry.write_text(entry_conf) + put(entry, 'loader/entry.conf') + + +def auto_esp_size(files: list[Path], headroom_mib: int = 16) -> int: + """Sum file sizes + headroom, round up to MiB, floor at 64 MiB (FAT32)""" + total = sum(f.stat().st_size for f in files) + headroom_mib * MIB + mib = (total + MIB - 1) // MIB + return max(mib, 64) + + +def repack_iso( + in_iso: Path, out_iso: Path, esp_img: Path, esp_guid: str, +) -> None: + """Stream the input ISO to a new ISO, replacing partition 2's data. + + -boot_image any replay preserves every other boot record (BIOS El Torito, + grub2 MBR, GPT layout); only the bytes behind partition 2 are rewritten + """ + command.run( + 'xorriso', + '-indev', str(in_iso), + '-outdev', str(out_iso), + '-boot_image', 'any', 'replay', + '-append_partition', '2', esp_guid, str(esp_img), + '-commit', + ) + + +def main() -> None: + p = argparse.ArgumentParser( + description='Rewrite an Ubuntu live ISO to boot via U-Boot + BLS.', + ) + p.add_argument('iso', type=Path, help='input Ubuntu live ISO') + p.add_argument('-u', '--uboot', type=Path, required=True, + help='U-Boot EFI app (e.g. u-boot-app.efi)') + p.add_argument('-o', '--out', type=Path, required=True, + help='output ISO path') + p.add_argument('-k', '--kernel', default='casper/vmlinuz', + help='kernel path inside the input ISO') + p.add_argument('-i', '--initrd', default='casper/initrd', + help='initrd path inside the input ISO') + p.add_argument('-a', '--cmdline', default=DEFAULT_CMDLINE, + help='kernel command line written into loader/entry.conf') + p.add_argument('-t', '--title', default=None, + help='BLS entry title (default: derived from volume label)') + p.add_argument('-s', '--esp-size', type=int, default=None, + help='ESP size in MiB (default: auto-size to fit)') + args = p.parse_args() + + if not args.iso.is_file(): + tout.fatal(f'ISO not found: {args.iso}') + if not args.uboot.is_file(): + tout.fatal(f'EFI app not found: {args.uboot}') + check_tools() + + print(f'=> Reading boot config from {args.iso}') + # Extract the volume label and ESP partition GUID from xorriso's report + vol_id, esp_guid = parse_boot_report(args.iso) + title = args.title or f'U-Boot BLS boot ({vol_id})' + print(f' Volume label: {vol_id}') + print(f' ESP GUID: {esp_guid}') + + with tempfile.TemporaryDirectory(prefix='iso2uboot.') as td: + work = Path(td) + vmlinuz = work / 'vmlinuz' + initrd = work / 'initrd' + + print(f'=> Extracting /{args.kernel} and /{args.initrd}') + extract_from_iso(args.iso, args.kernel, vmlinuz) + extract_from_iso(args.iso, args.initrd, initrd) + + esp_mib = args.esp_size or auto_esp_size([vmlinuz, initrd, args.uboot]) + print(f'=> Building {esp_mib} MiB ESP') + + entry_conf = f'''\ +title {title} +linux /casper/vmlinuz +initrd /casper/initrd +options {args.cmdline} +''' + esp = work / 'esp.img' + build_esp(esp, esp_mib, args.uboot, vmlinuz, initrd, entry_conf) + + print(f'=> Repacking to {args.out}') + repack_iso(args.iso, args.out, esp, esp_guid) + + size_mib = args.out.stat().st_size / MIB + print(f'=> Done: {args.out} ({size_mib:.1f} MiB)') + + +if __name__ == '__main__': + main()