HFS+ bootloader plan for FreeBSD

Companion to the HFS+ kernel module port plan and scopes issue #80 (boot-132 / modern derivative). The HFS+ kmod gets us read/write of HFS+ data partitions after the kernel is up; this plan addresses the prior question: how does the bootloader find the kernel if the boot partition is HFS+? Three options reviewed; recommendation: Option C (UFS for /boot, HFS+ for data) — zero bootloader work, and matches how every modern FreeBSD installer already lays out disks.

TL;DR. Don't port boot-132 or OpenCore. They're Darwin-platform loaders — you'd have to rip out the kext-cache + Mach boot-args + xnu-handoff to point them at FreeBSD, which is more work than writing an HFS+ reader from scratch. Just keep UFS for /boot like FreeBSD already does, and put HFS+ on the data partitions where the kmod can mount it. If a real "boot directly from HFS+ root" use case emerges later, add HFS+ to stand/libsa (~2-3 weeks) using Clover's VBoxHfs reader as the source.

Contents

  1. What we're solving
  2. Apple boot-132 (BIOS) — what it is, plus the UEFI boot path
  3. Modern descendants: OpenCore, Clover, Chameleon
  4. FreeBSD's stand/loader today
  5. Option A — Port boot-132 / OpenCore
  6. Option B — Add HFS+ reader to stand/libsa
  7. Option C — UFS for /boot, HFS+ for data (recommended)
  8. Decision matrix
  9. Out of scope

1. What we're solving

FreeBSD's stand/loader (the second-stage bootloader that runs after boot0/boot1) can read UFS, ZFS, ext2fs, ISO 9660, FAT/msdosfs, and NFS. It cannot read HFS+. So an installation with HFS+ on /boot can't start — the loader physically can't find kernel.bin or loader.conf on disk.

If we want HFS+ anywhere on a freebsd-launchd-mach system, three patterns satisfy that:

  1. Replace FreeBSD's loader with an HFS+-capable one (boot-132 family).
  2. Teach FreeBSD's loader to read HFS+ (add one file to stand/libsa).
  3. Don't put HFS+ on /boot — use UFS for the boot partition, HFS+ for whatever data we want.

The HFS+ kmod port (separate plan) handles all post-kernel HFS+ access regardless of which option we pick here.

2. Apple boot-132 (BIOS) — what it is, plus the UEFI boot path

AttributeStatus
Repositoryapple-oss-distributions/boot
Last releaseboot-132, October 24, 2006. Final release Apple ever published. Seven tags total, nothing newer in ~20 years.
LicenseAPSL 2.0 (Aug 6, 2003)
Architecturei386 only. No x86_64, no PPC (PPC BootX was separate, also abandoned).
Size~30k LOC C + assembly (90% C, 6% asm)
Boot model3-stage BIOS chainloader. No UEFI. Pre-boot.efi.
Partition tableMBR only. GPT support added by community forks (Chameleon, Clover), not Apple.
Successorboot.efi (10.6+), iBoot (iOS/Apple Silicon). Both closed-source; never published.

What boot-132 actually does:

  1. boot0 — MBR sector-0 stub; finds the active partition.
  2. boot1h — HFS+ partition boot sector; locates /boot in the HFS+ catalog B-tree.
  3. boot (stage 2) — reads mach_kernel + Extensions.mkext (kext cache) from HFS+; parses com.apple.Boot.plist for kernel args; sets up the Mach boot-args page; switches to protected mode; jumps to xnu's entry.

The handoff at the end of stage 2 is completely Darwin-specific: Mach boot-args page, kext cache pointer, specific xnu entry contract. Pointing this code at FreeBSD's kernel.bin means replacing the entire handoff and the kext-cache logic with FreeBSD's elf_freebsd_exec/loader interaction — at which point you've reimplemented stand/loader, just badly.

Anatomy of a classic HFS+ boot volume (pre-EFI / pre-APFS) Firmware(BIOS) boot0MBR sector 0 boot1hHFS+ boot blocks boot (stage 2)the file /boot XNU/mach_kernel HFS+ volume — the bootable disk (what boot-132 reads) Volume boot blocks (reserved sectors) → boot1h: partition boot sector — walks the HFS+ catalog B-tree to find /boot / — volume root (HFS+ catalog B-tree) /boot stage-2 boot loader — a FILE at the root, NOT a directory /mach_kernel the XNU kernel image, loaded by stage 2 /System/Library/Extensions.mkext prelinked kext cache (the drivers XNU needs at boot) /Library/Preferences/SystemConfiguration/com.apple.Boot.plist kernel boot-args / config parsed by stage 2 macOS never had a /boot directory. In the HFS+ era, “/boot” was the stage-2 loader file at the volume root, and the kernel was /mach_kernel. Modern macOS is APFS: the boot files moved into a hidden APFS “Preboot” volume (boot.efi + kernelcache / BootKernelExtensions.kc), and the System volume is a sealed read-only snapshot. That is why ls / on a current Mac shows bin/sbin/System/… but no /boot and no mach_kernel.
Classic HFS+ boot chain and on-disk layout. boot-132 expects every one of these Darwin-specific artifacts (mach_kernel, Extensions.mkext, com.apple.Boot.plist) — none of which a FreeBSD/NextBSD system produces — which is the core reason §5 recommends against porting it.

The UEFI boot path (Apple EFI · generic UEFI · FreeBSD loader.efi)

boot-132 is a BIOS loader: the chain above (boot0 MBR sector → boot1h partition sector → the /boot file) is pure MBR/BIOS and has no UEFI equivalent. Under UEFI the firmware itself owns a filesystem driver, reads the GPT, and launches a PE/COFF .efi application off a partition — there is no boot0/boot1h sector chain at all. Three cases matter for HFS+:

UEFI boot — and where HFS+ fits (contrast with the BIOS chain above) Apple Mac (EFI) Apple EFI firmwarebuilt-in HFS+ driver boot.efi/System/Library/CoreServices kernelcache(prelinkedkernel) XNU Generic UEFI UEFI firmwarereads FAT (ESP) only ESP (FAT): EFI HFS+ driverVBoxHfs.efi / HfsPlus.efi + OpenCore/Clover HFS+ volumekernel + kexts FreeBSD/ NextBSD UEFI firmwarereads FAT (ESP) ESP (FAT): loader.efiFreeBSD UEFI loader kernel + loader.conf via libsaUFS/ZFS today; HFS+ only with the Option B reader Vs. BIOS (the diagram above): • No boot0/boot1h raw sectors — UEFI runs a .efi application off a partition, read via the firmware's own FS driver. • Only Apple's firmware reads HFS+ natively, so boot.efi loads straight off the HFS+ system volume. • Generic UEFI firmware reads only FAT (the ESP); HFS+-at-boot needs an EFI HFS+ driver (what OpenCore/Clover ship). • FreeBSD's loader.efi finds the kernel through shared stand/libsa FS code — the same place an HFS+ reader (Option B) would live, so one ~1.5–3 kLOC reader serves BIOS and UEFI both.
The three UEFI realities for HFS+. boot-132 (the BIOS diagram above) has no place here — UEFI HFS+ support is either firmware-native (Apple), an EFI driver (OpenCore/Clover), or a libsa reader shared by loader/loader.efi (Option B).

This is why the recommendation is firmware-agnostic. Option C (UFS/ZFS for /boot + the FAT ESP that UEFI already requires, HFS+ for data) needs nothing on either BIOS or UEFI. Option B's single libsa reader serves both. Only boot-132 (Option A) is BIOS-only — UEFI support would mean bolting on a separate EFI HFS+ driver (OpenCore territory), which is the bulk of why §5 rejects it.

3. Modern descendants: OpenCore, Clover, Chameleon

ProjectLicenseStatusFS supportBoot model
OpenCorePkg BSD-3-Clause Active; v1.0.7 March 20, 2025; 5,005 commits HFS+ via closed-Apple-derived HfsPlus.efi binary blob in OcBinaryData; APFS via Apple's container-embedded driver; FAT native; ext4/btrfs via plug-ins Pure UEFI
Clover BSD-2-Clause Active; release-5172g, March 22, 2026; 2,489 commits HFS+ via VBoxHfs.efi (VirtualBox-derived, LGPL-compatible); APFS via Apple's driver; FAT, ext, NTFS BIOS + UEFI
Chameleon (meklort fork) APSL 2.0 Dormant since ~2014 HFS+, FAT32, ext2, GPT+MBR BIOS (closest to boot-132)

Architectural reality: all three exist to boot macOS/Darwin. Their reason for being is the Apple-specific bring-up dance (SMBIOS spoofing, ACPI patching, ApplePlatformInfo emulation, EfiBoot protocol, kext injection). For our purposes we'd be importing ~50–200k LOC of macOS-emulation work to use the ~3-5k LOC HFS+ reader buried inside. That's a maintenance trap.

What's useful from this ecosystem: the standalone HFS+ readers they each ship are extractable.

4. FreeBSD's stand/loader today

From freebsd-src/stand/libsa:

FilesystemFileApprox LOC
UFSufs.c, ufsread.c~1500
ext2fsext2fs.c~1200
ISO 9660cd9660.c, cd9660read.c~600
FAT/msdosfsdosfs.c~1100
NFSnfs.c~900
ZFSzfs/ subdir~larger
HFS+(none, never has been)

The loader's plug-in FS ABI is simple: each filesystem implements fs_open/close/read/write/seek/stat/readdir via a struct fs_ops exposed through libsa. Adding a new reader is a single C file. UFS is ~1500 LOC, ext2fs ~1200, cd9660 ~600 — an HFS+ read-only reader for libsa would fit in the same ~1.5–3 kLOC envelope, far smaller than the EDK2-style EFI drivers because libsa provides its own buffered-block I/O (no UEFI protocol marshalling).

5. Option A — Port boot-132 / OpenCore

What it is: import Apple's boot-132 source (or OpenCore) into our tree, modify the stage-2 handoff to point at FreeBSD's kernel.bin instead of mach_kernel, replace the kext-cache logic with whatever FreeBSD's loader does to find modules, and wire it as the live ISO's bootloader.

Effort: rough lower bound 3 person-months. Most of the work isn't the HFS+ reader (which is already wired) — it's gutting the Darwin handoff and replacing it with FreeBSD's loader contract. Boot-132's BIOS-only model and i386-only support also mean we'd be writing the EFI side too. OpenCore brings EFI for free but drags in vastly more macOS-emulation code we'd have to either disable or maintain.

Tradeoffs:

Verdict: not recommended.

6. Option B — Add HFS+ reader to stand/libsa

What it is: write a new stand/libsa/hfs.c that implements fs_ops for HFS+ read-only. Wire it into stand/loader.mk behind a LOADER_HFSPLUS_SUPPORT switch. Source material: port Clover's VBoxFsDxe/VBoxHfs* (LGPL, clean BSD/LGPL-compatible) into libsa's idiom, dropping the EDK2 protocol wrapping in favor of libsa's fs_ops ABI.

Effort: 2–3 weeks for someone experienced with libsa; ~1–1.5 person-months for a junior dev. Add a week if we also want an EFI-driver standalone version of the same reader for the ESP (EFI_SIMPLE_FILE_SYSTEM_PROTOCOL wrapping around the same core).

What it delivers:

Tradeoffs:

Verdict: the right answer if a real use case emerges. Don't write it preemptively.

7. Option C — UFS for /boot, HFS+ for data recommended

What it is: partition the disk so /boot is UFS (or ZFS, or msdosfs ESP). HFS+ lives on data partitions where the kmod can mount it post-kernel-load. Modern FreeBSD installers already do this — EFI System Partition (FAT) + freebsd-boot + freebsd-ufs (root) + freebsd-swap; HFS+ becomes another freebsd-data-style partition.

Effort: zero. Nothing to build, nothing to change in the loader.

What it delivers: the HFS+ kmod handles all post-boot HFS+ workflows (mount Apple DMG images, read/write external Apple drives, Time Machine sparsebundle inspection, etc.). The cost is one extra small partition for /boot — which is standard hygiene anyway.

Tradeoffs:

8. Decision matrix

DimensionA: Port boot-132/OpenCoreB: Add HFS+ to libsaC: UFS for /boot
Effort3+ person-months2-3 weeks0
Boots from HFS+ rootYesYesNo (UFS root)
HFS+ data works (with kmod)YesYesYes
Mount Apple DMGs (with kmod + dmg2img)YesYesYes
Maintenance burdenHigh (~30k LOC bootloader)Low (~1.5-3k LOC libsa file)None
License postureAPSL 2.0 (Apple-aligned)LGPL via VBoxHfs OR APSL 2.0 via boot-132n/a
Upstream-able to FreeBSDNoMaybe (one-file libsa addition)n/a

9. Out of scope

Drafted 2026-05-26 from an agent research pass against Apple boot-132, OpenCore/Clover/Chameleon source repos, FreeBSD stand/libsa tree, and rEFInd/EDK2 HFS+ EFI driver ecosystem. Companion to HFS+ kernel module port plan and hdiutil port plan. Scopes issue #80. Sources: apple-oss-distributions/boot, OpenCorePkg, CloverBootloader, FreeBSD stand/libsa.