-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfreebsd-hfsplus-bootloader-plan.html
More file actions
403 lines (342 loc) · 33.3 KB
/
Copy pathfreebsd-hfsplus-bootloader-plan.html
File metadata and controls
403 lines (342 loc) · 33.3 KB
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HFS+ bootloader plan for FreeBSD — boot-132 vs libsa vs UFS-for-/boot</title>
<style>
:root {
--fg: #1a1a1a; --fg-muted: #555; --bg: #fafaf7; --accent: #b8472a;
--accent-soft: #f3e7df; --border: #d8d4c8; --code-bg: #f0ece2;
--table-stripe: #f4efe5; --warn: #8a5a00; --good: #2d6f3b; --bad: #a23030;
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body { font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, sans-serif; color: var(--fg); background: var(--bg); line-height: 1.55; margin: 0; padding: 0; }
.wrap { max-width: 880px; margin: 0 auto; padding: 48px 32px 96px; }
h1 { font-size: 2.1rem; line-height: 1.2; margin: 0 0 8px; letter-spacing: -0.01em; }
h2 { font-size: 1.45rem; margin: 56px 0 12px; padding-top: 18px; border-top: 2px solid var(--border); letter-spacing: -0.005em; }
h3 { font-size: 1.15rem; margin: 32px 0 10px; color: var(--accent); }
p { margin: 0 0 14px; }
ul, ol { margin: 0 0 14px 22px; padding: 0; }
li { margin-bottom: 4px; }
code, pre { font-family: "SF Mono", Menlo, Consolas, monospace; background: var(--code-bg); color: var(--fg); }
code { padding: 1px 5px; border-radius: 3px; font-size: 0.92em; }
pre { padding: 14px 18px; border-radius: 4px; overflow-x: auto; font-size: 0.86rem; line-height: 1.45; margin: 0 0 18px; }
a { color: var(--accent); }
a:hover { text-decoration: underline; }
.lede { font-size: 1.05rem; color: var(--fg-muted); margin-bottom: 32px; }
table { width: 100%; border-collapse: collapse; margin: 14px 0 22px; font-size: 0.91rem; }
th, td { text-align: left; padding: 8px 10px; border: 1px solid var(--border); vertical-align: top; }
th { background: var(--accent-soft); font-weight: 600; }
tr:nth-child(even) td { background: var(--table-stripe); }
.callout { border-left: 3px solid var(--accent); background: var(--accent-soft); padding: 14px 18px; margin: 18px 0 22px; border-radius: 0 4px 4px 0; }
.callout p:last-child { margin-bottom: 0; }
.callout-good { border-left-color: var(--good); background: #e8f1e3; }
.callout-warn { border-left-color: var(--warn); background: #fbf3df; }
.callout-bad { border-left-color: var(--bad); background: #fbe6e6; }
.pill { display: inline-block; font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; padding: 2px 7px; border-radius: 10px; margin-right: 6px; }
.pill-good { background: #d6ead0; color: var(--good); }
.pill-warn { background: #f4dfbf; color: var(--warn); }
.pill-bad { background: #f0c8c8; color: var(--bad); }
.toc { background: white; border: 1px solid var(--border); border-radius: 4px; padding: 18px 24px 14px 36px; margin: 0 0 36px; font-size: 0.95rem; }
.toc h2 { margin: 0 0 8px; padding-top: 0; border-top: none; font-size: 1rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--fg-muted); margin-left: -14px; }
.toc ol { margin: 0 0 0 6px; }
.toc a { color: var(--fg); text-decoration: none; }
.toc a:hover { text-decoration: underline; }
.footnote { font-size: 0.85rem; color: var(--fg-muted); border-top: 1px solid var(--border); margin-top: 48px; padding-top: 16px; }
@media (max-width: 600px) { .wrap { padding: 24px 18px 64px; } }
</style>
</head>
<body>
<div class="wrap">
<h1>HFS+ bootloader plan for FreeBSD</h1>
<p class="lede">Companion to the <a href="freebsd-hfsplus-port-plan.html">HFS+ kernel module port plan</a> and scopes <a href="https://github.com/pkgdemon/freebsd-launchd-mach/issues/80">issue #80 (boot-132 / modern derivative)</a>. The HFS+ kmod gets us read/write of HFS+ data partitions <em>after</em> the kernel is up; this plan addresses the prior question: <strong>how does the bootloader find the kernel if the boot partition is HFS+?</strong> Three options reviewed; <strong>recommendation: Option C (UFS for <code>/boot</code>, HFS+ for data) — zero bootloader work, and matches how every modern FreeBSD installer already lays out disks.</strong></p>
<div class="callout callout-good">
<p><strong>TL;DR.</strong> 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 <code>/boot</code> 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 <code>stand/libsa</code> (~2-3 weeks) using Clover's VBoxHfs reader as the source.</p>
</div>
<div class="toc">
<h2>Contents</h2>
<ol>
<li><a href="#problem">What we're solving</a></li>
<li><a href="#boot132">Apple boot-132 (BIOS) — what it is, plus the UEFI boot path</a></li>
<li><a href="#modern-forks">Modern descendants: OpenCore, Clover, Chameleon</a></li>
<li><a href="#freebsd-loader">FreeBSD's <code>stand/loader</code> today</a></li>
<li><a href="#option-a">Option A — Port boot-132 / OpenCore</a></li>
<li><a href="#option-b">Option B — Add HFS+ reader to <code>stand/libsa</code></a></li>
<li><a href="#option-c">Option C — UFS for <code>/boot</code>, HFS+ for data (recommended)</a></li>
<li><a href="#decision-matrix">Decision matrix</a></li>
<li><a href="#out-of-scope">Out of scope</a></li>
</ol>
</div>
<h2 id="problem">1. What we're solving</h2>
<p>FreeBSD's <code>stand/loader</code> (the second-stage bootloader that runs after <code>boot0</code>/<code>boot1</code>) can read UFS, ZFS, ext2fs, ISO 9660, FAT/msdosfs, and NFS. It cannot read HFS+. So an installation with HFS+ on <code>/boot</code> can't start — the loader physically can't find <code>kernel.bin</code> or <code>loader.conf</code> on disk.</p>
<p>If we want HFS+ anywhere on a <code>freebsd-launchd-mach</code> system, three patterns satisfy that:</p>
<ol>
<li>Replace FreeBSD's loader with an HFS+-capable one (boot-132 family).</li>
<li>Teach FreeBSD's loader to read HFS+ (add one file to <code>stand/libsa</code>).</li>
<li>Don't put HFS+ on <code>/boot</code> — use UFS for the boot partition, HFS+ for whatever data we want.</li>
</ol>
<p>The HFS+ kmod port (separate plan) handles all post-kernel HFS+ access regardless of which option we pick here.</p>
<h2 id="boot132">2. Apple boot-132 (BIOS) — what it is, plus the UEFI boot path</h2>
<table>
<tr><th>Attribute</th><th>Status</th></tr>
<tr><td>Repository</td><td><a href="https://github.com/apple-oss-distributions/boot">apple-oss-distributions/boot</a></td></tr>
<tr><td>Last release</td><td><code>boot-132</code>, October 24, 2006. <strong>Final release Apple ever published.</strong> Seven tags total, nothing newer in ~20 years.</td></tr>
<tr><td>License</td><td>APSL 2.0 (Aug 6, 2003)</td></tr>
<tr><td>Architecture</td><td>i386 only. No x86_64, no PPC (PPC <code>BootX</code> was separate, also abandoned).</td></tr>
<tr><td>Size</td><td>~30k LOC C + assembly (90% C, 6% asm)</td></tr>
<tr><td>Boot model</td><td>3-stage BIOS chainloader. No UEFI. Pre-<code>boot.efi</code>.</td></tr>
<tr><td>Partition table</td><td>MBR only. GPT support added by community forks (Chameleon, Clover), not Apple.</td></tr>
<tr><td>Successor</td><td><code>boot.efi</code> (10.6+), <code>iBoot</code> (iOS/Apple Silicon). Both <strong>closed-source</strong>; never published.</td></tr>
</table>
<p><strong>What boot-132 actually does:</strong></p>
<ol>
<li><code>boot0</code> — MBR sector-0 stub; finds the active partition.</li>
<li><code>boot1h</code> — HFS+ partition boot sector; locates <code>/boot</code> in the HFS+ catalog B-tree.</li>
<li><code>boot</code> (stage 2) — reads <code>mach_kernel</code> + <code>Extensions.mkext</code> (kext cache) from HFS+; parses <code>com.apple.Boot.plist</code> for kernel args; sets up the Mach boot-args page; switches to protected mode; jumps to xnu's entry.</li>
</ol>
<p>The handoff at the end of stage 2 is <strong>completely Darwin-specific</strong>: Mach boot-args page, kext cache pointer, specific xnu entry contract. Pointing this code at FreeBSD's <code>kernel.bin</code> means replacing the entire handoff and the kext-cache logic with FreeBSD's <code>elf_freebsd_exec</code>/loader interaction — at which point you've reimplemented <code>stand/loader</code>, just badly.</p>
<figure style="margin:22px 0 26px;text-align:center;">
<svg viewBox="0 0 840 590" width="100%" style="max-width:840px;border:1px solid #d8d4c8;border-radius:6px;background:#fafaf7;font-family:-apple-system,Helvetica,Arial,sans-serif;">
<defs>
<marker id="ah" markerWidth="9" markerHeight="9" refX="6" refY="3" orient="auto">
<path d="M0,0 L6,3 L0,6 Z" fill="#b8472a"/>
</marker>
</defs>
<text x="420" y="24" text-anchor="middle" font-size="15" font-weight="700" fill="#b8472a">Anatomy of a classic HFS+ boot volume (pre-EFI / pre-APFS)</text>
<!-- boot chain -->
<g font-size="12.5" font-weight="600" fill="#1a1a1a" text-anchor="middle">
<rect x="8" y="44" width="116" height="44" rx="5" fill="#f3e7df" stroke="#b8472a"/>
<text x="66" y="64">Firmware</text><text x="66" y="80" font-size="10.5" font-weight="400" fill="#555">(BIOS)</text>
<rect x="152" y="44" width="132" height="44" rx="5" fill="#f3e7df" stroke="#b8472a"/>
<text x="218" y="64">boot0</text><text x="218" y="80" font-size="10.5" font-weight="400" fill="#555">MBR sector 0</text>
<rect x="320" y="44" width="156" height="44" rx="5" fill="#f3e7df" stroke="#b8472a"/>
<text x="398" y="64">boot1h</text><text x="398" y="80" font-size="10.5" font-weight="400" fill="#555">HFS+ boot blocks</text>
<rect x="512" y="44" width="160" height="44" rx="5" fill="#f3e7df" stroke="#b8472a"/>
<text x="592" y="64">boot (stage 2)</text><text x="592" y="80" font-size="10.5" font-weight="400" fill="#555">the file /boot</text>
<rect x="708" y="44" width="124" height="44" rx="5" fill="#e8f1e3" stroke="#2d6f3b"/>
<text x="770" y="64">XNU</text><text x="770" y="80" font-size="10.5" font-weight="400" fill="#555">/mach_kernel</text>
</g>
<g stroke="#b8472a" stroke-width="1.5" marker-end="url(#ah)">
<line x1="124" y1="66" x2="150" y2="66"/><line x1="284" y1="66" x2="318" y2="66"/>
<line x1="476" y1="66" x2="510" y2="66"/><line x1="672" y1="66" x2="706" y2="66"/>
</g>
<!-- volume layout -->
<rect x="8" y="112" width="824" height="328" rx="6" fill="#ffffff" stroke="#d8d4c8"/>
<text x="24" y="134" font-size="13" font-weight="700" fill="#1a1a1a">HFS+ volume — the bootable disk (what boot-132 reads)</text>
<rect x="24" y="148" width="792" height="44" rx="4" fill="#f0ece2" stroke="#d8d4c8"/>
<text x="38" y="168" font-size="12" font-weight="600" fill="#1a1a1a">Volume boot blocks (reserved sectors)</text>
<text x="38" y="184" font-size="10.5" fill="#555">→ boot1h: partition boot sector — walks the HFS+ catalog B-tree to find /boot</text>
<rect x="24" y="204" width="792" height="224" rx="4" fill="#fbfaf6" stroke="#d8d4c8"/>
<text x="38" y="226" font-size="12" font-weight="700" fill="#1a1a1a">/ — volume root (HFS+ catalog B-tree)</text>
<g font-family="SF Mono,Menlo,Consolas,monospace">
<rect x="40" y="238" width="760" height="40" rx="4" fill="#f3e7df" stroke="#b8472a"/>
<text x="54" y="256" font-size="12.5" font-weight="700" fill="#b8472a">/boot</text>
<text x="54" y="272" font-size="10.5" font-family="-apple-system,Helvetica,Arial,sans-serif" fill="#555">stage-2 boot loader — a FILE at the root, NOT a directory</text>
<rect x="40" y="282" width="760" height="40" rx="4" fill="#f3e7df" stroke="#b8472a"/>
<text x="54" y="300" font-size="12.5" font-weight="700" fill="#b8472a">/mach_kernel</text>
<text x="54" y="316" font-size="10.5" font-family="-apple-system,Helvetica,Arial,sans-serif" fill="#555">the XNU kernel image, loaded by stage 2</text>
<rect x="40" y="326" width="760" height="40" rx="4" fill="#ffffff" stroke="#d8d4c8"/>
<text x="54" y="344" font-size="12" fill="#1a1a1a">/System/Library/Extensions.mkext</text>
<text x="54" y="360" font-size="10.5" font-family="-apple-system,Helvetica,Arial,sans-serif" fill="#555">prelinked kext cache (the drivers XNU needs at boot)</text>
<rect x="40" y="370" width="760" height="42" rx="4" fill="#ffffff" stroke="#d8d4c8"/>
<text x="54" y="388" font-size="11.5" fill="#1a1a1a">/Library/Preferences/SystemConfiguration/com.apple.Boot.plist</text>
<text x="54" y="404" font-size="10.5" font-family="-apple-system,Helvetica,Arial,sans-serif" fill="#555">kernel boot-args / config parsed by stage 2</text>
</g>
<!-- note -->
<rect x="8" y="452" width="824" height="128" rx="4" fill="#fbf3df" stroke="#8a5a00"/>
<rect x="8" y="452" width="4" height="128" fill="#8a5a00"/>
<g font-size="11.5" fill="#3a2e10" font-family="-apple-system,Helvetica,Arial,sans-serif">
<text x="24" y="476" font-weight="700">macOS never had a /boot directory.</text>
<text x="24" y="496">In the HFS+ era, “/boot” was the stage-2 loader <tspan font-weight="700">file</tspan> at the volume root, and the kernel was /mach_kernel.</text>
<text x="24" y="514">Modern macOS is APFS: the boot files moved into a hidden APFS “Preboot” volume (boot.efi +</text>
<text x="24" y="532">kernelcache / BootKernelExtensions.kc), and the System volume is a sealed read-only snapshot.</text>
<text x="24" y="550">That is why <tspan font-family="SF Mono,Menlo,monospace">ls /</tspan> on a current Mac shows bin/sbin/System/… but no /boot and no mach_kernel.</text>
</g>
</svg>
<figcaption style="font-size:0.85rem;color:#555;margin-top:8px;">Classic HFS+ boot chain and on-disk layout. boot-132 expects every one of these Darwin-specific artifacts (<code>mach_kernel</code>, <code>Extensions.mkext</code>, <code>com.apple.Boot.plist</code>) — none of which a FreeBSD/NextBSD system produces — which is the core reason §5 recommends against porting it.</figcaption>
</figure>
<h3 id="uefi">The UEFI boot path (Apple EFI · generic UEFI · FreeBSD <code>loader.efi</code>)</h3>
<p>boot-132 is a <em>BIOS</em> loader: the chain above (<code>boot0</code> MBR sector → <code>boot1h</code> partition sector → the <code>/boot</code> file) is pure MBR/BIOS and has <strong>no UEFI equivalent</strong>. Under UEFI the firmware itself owns a filesystem driver, reads the GPT, and launches a PE/COFF <code>.efi</code> application off a partition — there is no <code>boot0</code>/<code>boot1h</code> sector chain at all. Three cases matter for HFS+:</p>
<figure style="margin:22px 0 26px;text-align:center;">
<svg viewBox="0 0 840 470" width="100%" style="max-width:840px;border:1px solid #d8d4c8;border-radius:6px;background:#fafaf7;font-family:-apple-system,Helvetica,Arial,sans-serif;">
<defs><marker id="ah2" markerWidth="9" markerHeight="9" refX="6" refY="3" orient="auto"><path d="M0,0 L6,3 L0,6 Z" fill="#b8472a"/></marker></defs>
<text x="420" y="24" text-anchor="middle" font-size="15" font-weight="700" fill="#b8472a">UEFI boot — and where HFS+ fits (contrast with the BIOS chain above)</text>
<text x="14" y="78" font-size="11" font-weight="700" fill="#555">Apple Mac</text>
<text x="14" y="91" font-size="11" font-weight="700" fill="#555">(EFI)</text>
<g font-size="12" font-weight="600" fill="#1a1a1a" text-anchor="middle">
<rect x="110" y="46" width="186" height="54" rx="5" fill="#f3e7df" stroke="#b8472a"/>
<text x="203" y="69">Apple EFI firmware</text><text x="203" y="86" font-size="10.5" font-weight="400" fill="#555">built-in HFS+ driver</text>
<rect x="330" y="46" width="196" height="54" rx="5" fill="#ffffff" stroke="#d8d4c8"/>
<text x="428" y="69">boot.efi</text><text x="428" y="86" font-size="10" font-weight="400" fill="#555">/System/Library/CoreServices</text>
<rect x="560" y="46" width="150" height="54" rx="5" fill="#ffffff" stroke="#d8d4c8"/>
<text x="635" y="69">kernelcache</text><text x="635" y="86" font-size="10.5" font-weight="400" fill="#555">(prelinkedkernel)</text>
<rect x="744" y="46" width="88" height="54" rx="5" fill="#e8f1e3" stroke="#2d6f3b"/>
<text x="788" y="77">XNU</text>
</g>
<g stroke="#b8472a" stroke-width="1.5" marker-end="url(#ah2)">
<line x1="296" y1="73" x2="328" y2="73"/><line x1="526" y1="73" x2="558" y2="73"/><line x1="710" y1="73" x2="742" y2="73"/>
</g>
<text x="14" y="178" font-size="11" font-weight="700" fill="#555">Generic</text>
<text x="14" y="191" font-size="11" font-weight="700" fill="#555">UEFI</text>
<g font-size="12" font-weight="600" fill="#1a1a1a" text-anchor="middle">
<rect x="110" y="150" width="186" height="54" rx="5" fill="#f0ece2" stroke="#d8d4c8"/>
<text x="203" y="173">UEFI firmware</text><text x="203" y="190" font-size="10.5" font-weight="400" fill="#555">reads FAT (ESP) only</text>
<rect x="330" y="150" width="244" height="54" rx="5" fill="#f3e7df" stroke="#b8472a"/>
<text x="452" y="171">ESP (FAT): EFI HFS+ driver</text><text x="452" y="188" font-size="10" font-weight="400" fill="#555">VBoxHfs.efi / HfsPlus.efi + OpenCore/Clover</text>
<rect x="608" y="150" width="180" height="54" rx="5" fill="#ffffff" stroke="#d8d4c8"/>
<text x="698" y="173">HFS+ volume</text><text x="698" y="190" font-size="10.5" font-weight="400" fill="#555">kernel + kexts</text>
</g>
<g stroke="#b8472a" stroke-width="1.5" marker-end="url(#ah2)">
<line x1="296" y1="177" x2="328" y2="177"/><line x1="574" y1="177" x2="606" y2="177"/>
</g>
<text x="14" y="278" font-size="10.5" font-weight="700" fill="#555">FreeBSD/</text>
<text x="14" y="291" font-size="10.5" font-weight="700" fill="#555">NextBSD</text>
<g font-size="12" font-weight="600" fill="#1a1a1a" text-anchor="middle">
<rect x="110" y="250" width="186" height="54" rx="5" fill="#f0ece2" stroke="#d8d4c8"/>
<text x="203" y="273">UEFI firmware</text><text x="203" y="290" font-size="10.5" font-weight="400" fill="#555">reads FAT (ESP)</text>
<rect x="330" y="250" width="186" height="54" rx="5" fill="#e8f1e3" stroke="#2d6f3b"/>
<text x="423" y="273">ESP (FAT): loader.efi</text><text x="423" y="290" font-size="10.5" font-weight="400" fill="#555">FreeBSD UEFI loader</text>
<rect x="550" y="250" width="282" height="54" rx="5" fill="#ffffff" stroke="#d8d4c8"/>
<text x="691" y="271">kernel + loader.conf via libsa</text><text x="691" y="288" font-size="10" font-weight="400" fill="#555">UFS/ZFS today; HFS+ only with the Option B reader</text>
</g>
<g stroke="#b8472a" stroke-width="1.5" marker-end="url(#ah2)">
<line x1="296" y1="277" x2="328" y2="277"/><line x1="516" y1="277" x2="548" y2="277"/>
</g>
<rect x="8" y="326" width="824" height="134" rx="4" fill="#f3e7df" stroke="#b8472a"/>
<rect x="8" y="326" width="4" height="134" fill="#b8472a"/>
<g font-size="11.5" fill="#1a1a1a">
<text x="24" y="348" font-weight="700">Vs. BIOS (the diagram above):</text>
<text x="24" y="368">• No boot0/boot1h raw sectors — UEFI runs a .efi application off a partition, read via the firmware's own FS driver.</text>
<text x="24" y="386">• Only Apple's firmware reads HFS+ natively, so boot.efi loads straight off the HFS+ system volume.</text>
<text x="24" y="404">• Generic UEFI firmware reads only FAT (the ESP); HFS+-at-boot needs an EFI HFS+ driver (what OpenCore/Clover ship).</text>
<text x="24" y="422">• FreeBSD's loader.efi finds the kernel through shared stand/libsa FS code — the same place an HFS+ reader (Option B)</text>
<text x="44" y="440">would live, so one ~1.5–3 kLOC reader serves BIOS and UEFI both.</text>
</g>
</svg>
<figcaption style="font-size:0.85rem;color:#555;margin-top:8px;">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 <code>libsa</code> reader shared by <code>loader</code>/<code>loader.efi</code> (Option B).</figcaption>
</figure>
<ul>
<li><strong>Apple Intel Macs.</strong> Apple's EFI firmware ships a built-in HFS+ driver, so it reads the HFS+ system volume directly and launches <code>/System/Library/CoreServices/boot.efi</code>, which loads the prelinkedkernel/kernelcache and hands off to XNU. "Boot from HFS+" worked <em>because the firmware itself could read HFS+</em> — there was never a <code>/boot</code> file or a <code>boot1h</code> sector involved on EFI Macs.</li>
<li><strong>Generic PC UEFI (hackintosh).</strong> Stock firmware reads only FAT (the EFI System Partition). To boot HFS+ you must first load an EFI HFS+ driver (<code>VBoxHfs.efi</code> / <code>HfsPlus.efi</code>) — exactly what OpenCore/Clover ship, and the only reusable ~3–5k LOC buried in them (see §3).</li>
<li><strong>FreeBSD / NextBSD.</strong> Firmware → ESP (FAT) → <code>loader.efi</code>, which finds the kernel through the shared <code>stand/libsa</code> filesystem readers. It can't read HFS+ today — the same gap as the BIOS loader, and fixable in the same place: a <code>libsa</code> HFS+ reader compiles into <em>both</em> <code>loader</code> (BIOS) and <code>loader.efi</code> (UEFI), so one reader covers both firmwares.</li>
</ul>
<p>This is why the recommendation is firmware-agnostic. <strong>Option C</strong> (UFS/ZFS for <code>/boot</code> + the FAT ESP that UEFI already requires, HFS+ for data) needs <em>nothing</em> on either BIOS or UEFI. <strong>Option B</strong>'s single <code>libsa</code> reader serves both. Only <strong>boot-132 (Option A) is BIOS-only</strong> — UEFI support would mean bolting on a separate EFI HFS+ driver (OpenCore territory), which is the bulk of why §5 rejects it.</p>
<h2 id="modern-forks">3. Modern descendants: OpenCore, Clover, Chameleon</h2>
<table>
<tr><th>Project</th><th>License</th><th>Status</th><th>FS support</th><th>Boot model</th></tr>
<tr>
<td><a href="https://github.com/acidanthera/OpenCorePkg">OpenCorePkg</a></td>
<td>BSD-3-Clause</td>
<td>Active; v1.0.7 March 20, 2025; 5,005 commits</td>
<td>HFS+ via closed-Apple-derived <code>HfsPlus.efi</code> binary blob in <a href="https://github.com/acidanthera/OcBinaryData/blob/master/Drivers/HfsPlus.efi">OcBinaryData</a>; APFS via Apple's container-embedded driver; FAT native; ext4/btrfs via plug-ins</td>
<td>Pure UEFI</td>
</tr>
<tr>
<td><a href="https://github.com/CloverHackyColor/CloverBootloader">Clover</a></td>
<td>BSD-2-Clause</td>
<td>Active; release-5172g, March 22, 2026; 2,489 commits</td>
<td>HFS+ via <code>VBoxHfs.efi</code> (VirtualBox-derived, LGPL-compatible); APFS via Apple's driver; FAT, ext, NTFS</td>
<td>BIOS + UEFI</td>
</tr>
<tr>
<td>Chameleon (meklort fork)</td>
<td>APSL 2.0</td>
<td>Dormant since ~2014</td>
<td>HFS+, FAT32, ext2, GPT+MBR</td>
<td>BIOS (closest to boot-132)</td>
</tr>
</table>
<p><strong>Architectural reality:</strong> all three exist to <em>boot macOS/Darwin</em>. 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.</p>
<p><strong>What's useful from this ecosystem:</strong> the <strong>standalone HFS+ readers</strong> they each ship are extractable.</p>
<ul>
<li><strong>Clover's <code>VBoxHfs</code></strong> — LGPL, EDK2-style EFI driver, ~3-5k LOC, derived from VirtualBox's HFS+ reader, read-only, byte-swappable, no Apple binary dependency. <strong>Best candidate</strong> for re-use as the basis of a libsa-side reader.</li>
<li><strong>OpenCore's <code>OpenHfsPlus</code></strong> — the open variant; acidanthera <a href="https://github.com/acidanthera/bugtracker/issues/659">bug #659</a> calls it slow and unaudited.</li>
<li><strong>GRUB's HFS+ module</strong> — GPLv3, used by rEFInd via <a href="https://efi.akeo.ie/">efi.akeo.ie</a>'s driver bundle. License-incompatible with our preferred posture.</li>
<li><strong>PureDarwin's <code>HFSPlus_EFI</code></strong> — sparse, 6 commits, unclear licensing.</li>
<li><strong>boot-132's own <code>i386/boot2/hfs.c</code></strong> — APSL 2.0, oldest, last touched 2006.</li>
</ul>
<h2 id="freebsd-loader">4. FreeBSD's <code>stand/loader</code> today</h2>
<p>From <a href="https://github.com/freebsd/freebsd-src/tree/main/stand/libsa">freebsd-src/stand/libsa</a>:</p>
<table>
<tr><th>Filesystem</th><th>File</th><th>Approx LOC</th></tr>
<tr><td>UFS</td><td><code>ufs.c</code>, <code>ufsread.c</code></td><td>~1500</td></tr>
<tr><td>ext2fs</td><td><code>ext2fs.c</code></td><td>~1200</td></tr>
<tr><td>ISO 9660</td><td><code>cd9660.c</code>, <code>cd9660read.c</code></td><td>~600</td></tr>
<tr><td>FAT/msdosfs</td><td><code>dosfs.c</code></td><td>~1100</td></tr>
<tr><td>NFS</td><td><code>nfs.c</code></td><td>~900</td></tr>
<tr><td>ZFS</td><td><code>zfs/</code> subdir</td><td>~larger</td></tr>
<tr><td>HFS+</td><td>(none, never has been)</td><td>—</td></tr>
</table>
<p>The loader's plug-in FS ABI is simple: each filesystem implements <code>fs_open/close/read/write/seek/stat/readdir</code> via a <code>struct fs_ops</code> exposed through <code>libsa</code>. 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).</p>
<h2 id="option-a">5. Option A — Port boot-132 / OpenCore</h2>
<p><strong>What it is:</strong> import Apple's <code>boot-132</code> source (or OpenCore) into our tree, modify the stage-2 handoff to point at FreeBSD's <code>kernel.bin</code> instead of <code>mach_kernel</code>, replace the kext-cache logic with whatever FreeBSD's loader does to find modules, and wire it as the live ISO's bootloader.</p>
<p><strong>Effort:</strong> 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.</p>
<p><strong>Tradeoffs:</strong></p>
<ul>
<li><span class="pill pill-good">+</span> HFS+ reader already wired (the only real "pro")</li>
<li><span class="pill pill-bad">−</span> Massive code drag for Darwin-specific bring-up logic we don't want</li>
<li><span class="pill pill-bad">−</span> No Apple upstream — we'd be maintaining ~30k LOC of bootloader by ourselves</li>
<li><span class="pill pill-bad">−</span> Option A's effort is greater than writing the HFS+ reader from scratch into our existing loader (Option B)</li>
<li><span class="pill pill-bad">−</span> Two parallel bootloader lineages (FreeBSD's existing loader stays for non-HFS+ setups) is a maintenance liability</li>
</ul>
<p><strong>Verdict: not recommended.</strong></p>
<h2 id="option-b">6. Option B — Add HFS+ reader to <code>stand/libsa</code></h2>
<p><strong>What it is:</strong> write a new <code>stand/libsa/hfs.c</code> that implements <code>fs_ops</code> for HFS+ read-only. Wire it into <code>stand/loader.mk</code> behind a <code>LOADER_HFSPLUS_SUPPORT</code> switch. Source material: port Clover's <code>VBoxFsDxe/VBoxHfs*</code> (LGPL, clean BSD/LGPL-compatible) into libsa's idiom, dropping the EDK2 protocol wrapping in favor of libsa's <code>fs_ops</code> ABI.</p>
<p><strong>Effort:</strong> 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 (<code>EFI_SIMPLE_FILE_SYSTEM_PROTOCOL</code> wrapping around the same core).</p>
<p><strong>What it delivers:</strong></p>
<ul>
<li><code>/boot/loader</code> can read kernel + modules + loader.conf from HFS+</li>
<li>Works for BIOS and EFI loaders identically (loader is FS-agnostic above libsa)</li>
<li>Tested by the same CI that tests every other libsa filesystem</li>
<li>Reuses VBoxHfs's well-tested read path</li>
</ul>
<p><strong>Tradeoffs:</strong></p>
<ul>
<li><span class="pill pill-good">+</span> Tiny surface area (one C file)</li>
<li><span class="pill pill-good">+</span> Lives in the existing loader; no parallel bootloader to maintain</li>
<li><span class="pill pill-good">+</span> License-clean (LGPL via VBoxHfs, or APSL 2.0 if we instead clean-room from boot-132's <code>hfs.c</code>)</li>
<li><span class="pill pill-good">+</span> Read-only is fine for <code>/boot</code> — loader never writes</li>
<li><span class="pill pill-warn">~</span> Requires touching FreeBSD <code>stand/</code> code — if we want it upstream eventually, FreeBSD-src patch flow</li>
<li><span class="pill pill-bad">−</span> ~2-3 weeks of work for a feature only needed if we want HFS+ <em>root</em>, which Option C avoids without the work</li>
</ul>
<p><strong>Verdict: the right answer if a real use case emerges.</strong> Don't write it preemptively.</p>
<h2 id="option-c">7. Option C — UFS for <code>/boot</code>, HFS+ for data <span class="pill pill-good">recommended</span></h2>
<p><strong>What it is:</strong> partition the disk so <code>/boot</code> 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.</p>
<p><strong>Effort:</strong> <strong>zero.</strong> Nothing to build, nothing to change in the loader.</p>
<p><strong>What it delivers:</strong> 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 <code>/boot</code> — which is standard hygiene anyway.</p>
<p><strong>Tradeoffs:</strong></p>
<ul>
<li><span class="pill pill-good">+</span> Zero bootloader work</li>
<li><span class="pill pill-good">+</span> Matches FreeBSD's default install layout</li>
<li><span class="pill pill-good">+</span> Matches how Linux handled it for years (ext-on-/boot, anything-else on root)</li>
<li><span class="pill pill-good">+</span> Doesn't preclude Option B later if needed</li>
<li><span class="pill pill-warn">~</span> Means we can't say "freebsd-launchd-mach boots from an HFS+ root" — we'd have UFS-or-ZFS root with HFS+ as data</li>
<li><span class="pill pill-bad">−</span> If we ever ship onto Mac hardware that came formatted as pure-HFS+, the user has to repartition</li>
</ul>
<h2 id="decision-matrix">8. Decision matrix</h2>
<table>
<tr><th>Dimension</th><th>A: Port boot-132/OpenCore</th><th>B: Add HFS+ to libsa</th><th>C: UFS for /boot</th></tr>
<tr><td>Effort</td><td>3+ person-months</td><td>2-3 weeks</td><td>0</td></tr>
<tr><td>Boots from HFS+ root</td><td>Yes</td><td>Yes</td><td>No (UFS root)</td></tr>
<tr><td>HFS+ data works (with kmod)</td><td>Yes</td><td>Yes</td><td>Yes</td></tr>
<tr><td>Mount Apple DMGs (with kmod + dmg2img)</td><td>Yes</td><td>Yes</td><td>Yes</td></tr>
<tr><td>Maintenance burden</td><td>High (~30k LOC bootloader)</td><td>Low (~1.5-3k LOC libsa file)</td><td>None</td></tr>
<tr><td>License posture</td><td>APSL 2.0 (Apple-aligned)</td><td>LGPL via VBoxHfs OR APSL 2.0 via boot-132</td><td>n/a</td></tr>
<tr><td>Upstream-able to FreeBSD</td><td>No</td><td>Maybe (one-file libsa addition)</td><td>n/a</td></tr>
</table>
<h2 id="out-of-scope">9. Out of scope</h2>
<ul>
<li><strong>Apple Silicon boot.</strong> iBoot, LocalPolicy, SEP are closed and architecturally incompatible. Asahi's <code>m1n1</code> is a separate universe.</li>
<li><strong>FileVault 2 unlock.</strong> Needs CoreStorage / APFS encryption stack. Not in HFS+ scope at all.</li>
<li><strong>APFS booting.</strong> Separate filesystem; OpenCore needs Apple's own embedded <code>apfs.efi</code> blob, not portable. Different effort.</li>
<li><strong>UEFI Secure Boot signature chain for Apple platforms.</strong> Apple boot policy is closed.</li>
<li><strong><code>boot.efi</code> compatibility / Apple boot picker.</strong> We're not emulating a Mac; FreeBSD's loader UI is fine.</li>
<li><strong>HFS+ write from the loader.</strong> <code>/boot</code> is read-only from loader's perspective.</li>
<li><strong>Journal replay in the loader.</strong> Only matters for writes; read-only reader ignores the journal.</li>
</ul>
<p class="footnote">Drafted 2026-05-26 from an agent research pass against Apple <code>boot-132</code>, OpenCore/Clover/Chameleon source repos, FreeBSD <code>stand/libsa</code> tree, and rEFInd/EDK2 HFS+ EFI driver ecosystem. Companion to <a href="freebsd-hfsplus-port-plan.html">HFS+ kernel module port plan</a> and <a href="freebsd-hdiutil-port-plan.html">hdiutil port plan</a>. Scopes <a href="https://github.com/pkgdemon/freebsd-launchd-mach/issues/80">issue #80</a>. Sources: <a href="https://github.com/apple-oss-distributions/boot">apple-oss-distributions/boot</a>, <a href="https://github.com/acidanthera/OpenCorePkg">OpenCorePkg</a>, <a href="https://github.com/CloverHackyColor/CloverBootloader">CloverBootloader</a>, <a href="https://github.com/freebsd/freebsd-src/tree/main/stand/libsa">FreeBSD stand/libsa</a>.</p>
</div>
</body>
</html>