Repository navigation
Expand file tree
/
Copy pathfreebsd-hdiutil-port-plan.html
More file actions
459 lines (386 loc) · 33.2 KB
/
Copy pathfreebsd-hdiutil-port-plan.html
File metadata and controls
459 lines (386 loc) · 33.2 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
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>hdiutil/hdik port plan — four options for review</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); }
h4 { font-size: 1rem; margin: 24px 0 6px; color: var(--fg); }
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; }
pre code { background: transparent; padding: 0; font-size: inherit; }
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-warn { border-left-color: var(--warn); background: #fbf3df; }
.callout-good { border-left-color: var(--good); background: #e8f1e3; }
.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); }
.pill-neutral { background: #ddd; color: #333; }
.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 li { margin-bottom: 4px; }
.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; }
.option-card { border: 1px solid var(--border); border-radius: 6px; padding: 18px 22px; margin: 14px 0 26px; background: white; }
.option-card h3 { margin-top: 0; }
.decision-pending { background: #fff3cd; border: 1px dashed var(--warn); padding: 18px 24px; border-radius: 6px; margin: 24px 0; font-size: 0.95rem; }
@media (max-width: 600px) { .wrap { padding: 24px 18px 64px; } }
</style>
</head>
<body>
<div class="wrap">
<h1><code>hdiutil</code> / <code>hdik</code> port plan — four options</h1>
<p class="lede">Decision document for the <code>hdiutil</code>/<code>hdik</code>/DiskImages workstream on <code>freebsd-launchd-mach</code>. Spun out of the v3 <a href="freebsd-apple-userland-cmds-plan.html">userland-cmds plan §16 deferred scoping</a> after research into Darling's reimplementation revealed it's a partial-surface, copyleft-licensed substitute — not the drop-in replacement the original v3 deferral assumed. Four implementation paths are laid out below for later decision. <strong>No decision is being made in this document</strong>; it's for review and selection.</p>
<div class="decision-pending">
<strong>Decision status:</strong> <span class="pill pill-warn">PENDING</span> — reviewer to pick Option A, B, C, or D in §9 below. Until selected, the live ISO uses FreeBSD's <code>mdconfig</code>/<code>mdmfs</code> from <code>/usr/src</code> (transitional gap-filler manifest per the v3 plan).
</div>
<div class="toc">
<h2>Contents</h2>
<ol>
<li><a href="#problem">What we're solving</a></li>
<li><a href="#apple-hdiutil">What Apple's <code>hdiutil</code>/<code>hdik</code> actually do</a></li>
<li><a href="#darling-findings">What Darling reimplemented (and didn't)</a></li>
<li><a href="#freebsd-baseline">What FreeBSD provides today</a></li>
<li><a href="#option-a">Option A — Clean-room fresh-write (MIT/BSD)</a></li>
<li><a href="#option-b">Option B — Vendor <code>darling-dmg</code> as-is (GPL-3.0)</a></li>
<li><a href="#option-c">Option C — Vendor <code>darling-dmg</code> + extend to full surface</a></li>
<li><a href="#option-d">Option D — Defer indefinitely</a></li>
<li><a href="#decision-matrix">Decision matrix</a></li>
<li><a href="#cross-cutting">Cross-cutting concerns</a></li>
<li><a href="#staged-scope">Staged scope (if A or C selected)</a></li>
</ol>
</div>
<h2 id="problem">1. What we're solving</h2>
<p><code>freebsd-launchd-mach</code> is dropping <code>FreeBSD-runtime</code> + <code>FreeBSD-utilities</code> and replacing the userland with Apple-source ports. Per the <a href="freebsd-apple-userland-cmds-plan.html">userland-cmds v3 plan</a>, every Apple userland repo we have open-source for gets vendored + ported. <code>hdiutil</code>/<code>hdik</code> are the gap — Apple-closed, no source to vendor, yet expected to be present at Apple-canonical paths.</p>
<p>The function this fills on the live ISO: <strong>create, attach, manipulate disk images</strong> — both the daily-driver "mount a downloaded DMG" use case AND the install-image production case (creating system images, converting between sparse/compressed/RW formats, etc.). FreeBSD's existing <code>mdconfig</code>/<code>mdmfs</code> cover the underlying memory-disk plumbing but expose a totally different CLI and don't speak DMG format at all.</p>
<h2 id="apple-hdiutil">2. What Apple's <code>hdiutil</code>/<code>hdik</code> actually do</h2>
<p>Per the macOS 14 <code>hdiutil(1)</code> man page, <strong>~30 subcommands</strong>:</p>
<table>
<tr><th>Category</th><th>Subcommands</th><th>What it does</th></tr>
<tr><td><strong>Attach / detach</strong></td><td><code>attach</code>, <code>detach</code>, <code>eject</code>, <code>mount</code>, <code>unmount</code>, <code>mountvol</code></td><td>Bind a disk image to a synthetic <code>/dev/disk*</code> device and mount its filesystem(s).</td></tr>
<tr><td><strong>Create / format</strong></td><td><code>create</code>, <code>partitiondisk</code>, <code>erasedisk</code>, <code>fdisk</code></td><td>Make a new disk image (sized, sparse, sparse bundle, encrypted, with specific format).</td></tr>
<tr><td><strong>Convert</strong></td><td><code>convert</code>, <code>makehybrid</code></td><td>Transform between formats: UDIF, UDZO, UDBZ, UDRO, ULFO, sparse, sparse bundle, ISO, IMG, NDIF, DC42, etc. <code>makehybrid</code> produces ISO/HFS+ hybrid images for cross-platform optical media.</td></tr>
<tr><td><strong>Information</strong></td><td><code>info</code>, <code>imageinfo</code>, <code>isencrypted</code>, <code>plugins</code>, <code>pmap</code>, <code>fsid</code>, <code>checksum</code>, <code>verify</code></td><td>Metadata inspection, partition-map dump, integrity check.</td></tr>
<tr><td><strong>Maintenance</strong></td><td><code>compact</code>, <code>resize</code>, <code>segment</code>, <code>chpass</code></td><td>Shrink unused space, grow image, split into segments, change encryption password.</td></tr>
<tr><td><strong>Resource-fork tools</strong></td><td><code>udifrez</code>, <code>udifderez</code></td><td>Get/set UDIF resource entries inside a DMG.</td></tr>
<tr><td><strong>Optical-media</strong></td><td><code>burn</code></td><td>Burn a disk image to CD/DVD via Apple's burning infrastructure.</td></tr>
<tr><td><strong>Web-distribution</strong></td><td><code>internet-enable</code></td><td>Toggle the Safari "auto-open after download" flag.</td></tr>
<tr><td><strong>Low-level</strong></td><td><code>conversion</code>, <code>mediakit</code></td><td>MediaKit framework introspection.</td></tr>
</table>
<p><strong><code>hdik</code> is the lower-level worker</strong> that <code>hdiutil attach</code> shells out to. It does the actual disk-image attach (creates the <code>/dev/disk*</code> node via <code>IOHDIXController</code> kext), without all of <code>hdiutil</code>'s format conversion and metadata logic. On macOS today most attach paths go through <code>hdiutil</code>; <code>hdik</code> is the in-process helper.</p>
<p><strong>Source availability:</strong> all of the above is <strong>closed-source</strong>. <code>DiskImages.framework</code> (which both <code>hdiutil</code> and <code>hdik</code> link) has never been published. The kext (<code>IOHDIXController</code>) is also closed in modern macOS. Apple's <a href="https://opensource.apple.com">opensource.apple.com</a> has no <code>DiskImages</code>, no <code>hdiutil</code>, no <code>hdik</code>, no <code>IOHDIXController</code>. Public references: the <code>hdiutil(1)</code> man page, Apple Technote TN1150 (HFS+), and the UDIF format reverse-engineering work done by the open-source community (notably by Jonathan Levin and the Darling team).</p>
<h2 id="darling-findings">3. What Darling reimplemented (and didn't)</h2>
<p>Two research deep-dives (2026-05-26, this session) confirmed the following coverage in the Darling project:</p>
<table>
<tr><th>Darling artifact</th><th>What it provides</th><th>Status</th></tr>
<tr><td><code>darling-dmg</code> (standalone repo)</td><td>FUSE-based, read-only <code>hdiutil attach</code> + <code>detach</code>. Parses UDIF containers (UDZO/UDBZ/UDRO/ULFO/Raw/ADC). From-scratch HFS+ reader (catalog B-tree, extents, attributes, resource forks, transparent zlib compression). Reads partition maps (APM, GPT). ~9 KB hdiutil shim + ~15 KB HFS+ B-tree + ~38 KB total source.</td><td>Working but narrow</td></tr>
<tr><td><code>darling-dmg</code> license</td><td>GPL-3.0 (full FSF text in LICENSE)</td><td>Copyleft</td></tr>
<tr><td><code>hdik</code> binary</td><td>Nothing</td><td>Never reimplemented</td></tr>
<tr><td><code>DiskImages.framework</code></td><td>~38 KB of <code>void* foo(void) { return NULL; }</code> stubs to satisfy dynamic link references. No functional behavior.</td><td>Link-stub only</td></tr>
<tr><td><code>diskutil</code></td><td>999-byte shell script. One verb (<code>eject</code> → shells to <code>hdiutil detach</code>). All others print "did not recognize verb".</td><td>Effectively nothing</td></tr>
<tr><td><code>asr</code> (Apple Software Restore)</td><td>Nothing</td><td>Completely missing</td></tr>
<tr><td><code>hdiejectd</code>, <code>diskimages-helper</code>, <code>IOHDIXController</code>, <code>vsdbutil</code>, <code>MediaKit.framework</code></td><td>Nothing</td><td>All completely missing</td></tr>
</table>
<p><strong>Darling's <code>hdiutil</code> coverage vs Apple's:</strong></p>
<table>
<tr><th>Apple subcommand category</th><th>darling-dmg coverage</th></tr>
<tr><td>Attach/detach</td><td>2 of 6 subcommands (<code>attach</code>, <code>detach</code>); read-only; ~60% of <code>attach</code>'s flags</td></tr>
<tr><td>Create/format</td><td>0 of 4</td></tr>
<tr><td>Convert</td><td>0 of 2</td></tr>
<tr><td>Information</td><td>0 of 8 (BLKX checksums are parsed but never validated)</td></tr>
<tr><td>Maintenance</td><td>0 of 4</td></tr>
<tr><td>Resource-fork</td><td>0 of 2</td></tr>
<tr><td>Optical-media</td><td>0 of 1</td></tr>
<tr><td>Web-distribution</td><td>0 of 1</td></tr>
<tr><td>Low-level</td><td>0 of 2</td></tr>
<tr><td><strong>Total coverage</strong></td><td><strong>~7% of subcommand surface</strong> (2/30)</td></tr>
</table>
<p>Functional coverage for the common case "open a downloaded UDZO/ULFO DMG, read files out of the HFS+ volume" is <strong>high</strong>. That one path is the project's entire goal.</p>
<p><strong>Architecture:</strong> pure userland FUSE 2.x. No kernel helper, no <code>/dev/disk*</code> node creation. <code>hdiutil attach</code> daemonizes <code>darling-dmg</code>, which mounts the HFS+ volume via FUSE; <code>hdiutil detach</code> shells to <code>fusermount -u</code>.</p>
<p><strong>FreeBSD portability:</strong> excellent. The source already has <code>#ifdef __FreeBSD__</code> branches in <code>src/be.h</code>; no Linux-specific headers (<code>linux/fs.h</code>, sysfs, <code>/proc</code>, FIEMAP) anywhere in <code>src/</code>; only <code><sys/stat.h></code> + POSIX. FreeBSD ships <code>fusefs-libs</code> (libfuse 2.9.x, ABI-compatible) and the <code>fusefs(5)</code> kmod. <code>darling-dmg</code> should compile out of the box after <code>pkg install fusefs-libs icu libxml2 openssl</code>. The Darling-specific shim (<code>main-hdiutil.cpp</code>) needs replacing with a ~150-line native wrapper (no <code><elfcalls.h></code>, no <code>__darling_vchroot_expand</code>).</p>
<h2 id="freebsd-baseline">4. What FreeBSD provides today</h2>
<table>
<tr><th>Tool</th><th>What it does</th><th>Comparable to Apple's...</th></tr>
<tr><td><code>mdconfig(8)</code></td><td>Create/destroy <code>md(4)</code> memory disks (vnode-backed, malloc-backed, swap-backed). Returns <code>/dev/md*</code> node.</td><td>The <em>attach</em> primitive only. No format parsing, no compression, no encryption.</td></tr>
<tr><td><code>mdmfs(8)</code></td><td>Wrapper: <code>mdconfig</code> + <code>newfs</code> + <code>mount</code> in one step to make a memory-disk-backed UFS volume.</td><td>Roughly <code>hdiutil attach -nomount</code> followed by Disk Utility format.</td></tr>
<tr><td><code>fdisk</code>, <code>gpart</code>, <code>bsdlabel</code></td><td>Partition-table tooling.</td><td><code>hdiutil pmap</code>, <code>hdiutil partitiondisk</code> partially.</td></tr>
<tr><td><strong>(missing)</strong></td><td>DMG/UDIF format parsing</td><td>Most of <code>hdiutil</code>'s value.</td></tr>
<tr><td><strong>(missing)</strong></td><td>HFS+ filesystem reader (no kmod, no fuse module shipped)</td><td>Required to mount Mac DMGs.</td></tr>
<tr><td><strong>(missing)</strong></td><td>APFS reader</td><td>Required for modern Mac DMGs (post-10.13 system images).</td></tr>
</table>
<p>FreeBSD's <code>md(4)</code> is the kernel-side analogue of Apple's IOHDIXController in terms of "expose a file as a block device," but it doesn't parse any disk-image format — it takes a raw file or already-formatted volume and exposes it. All format/compression/encryption logic on Apple lives in userland <code>hdiutil</code> + <code>DiskImages.framework</code>. So the Apple-shape "<code>hdiutil attach foo.dmg</code>" requires: (1) a DMG parser to find the embedded HFS+/APFS volume, (2) an HFS+/APFS reader to actually mount it, (3) the binding of (2)'s output to a synthetic <code>/dev/disk*</code> via <code>md(4)</code>. FreeBSD has only (3).</p>
<h2 id="option-a">5. Option A — Clean-room fresh-write (MIT/BSD)</h2>
<div class="option-card">
<h3>Apple-shape <code>hdiutil</code> written from scratch into <code>src/hdiutil/</code></h3>
<p><strong>What it is:</strong> implement <code>hdiutil</code>'s CLI surface from scratch in C/C++, written against Apple's published man page + Apple Technote TN1150 (HFS+) + the public UDIF format documentation. License under MIT or BSD-2-Clause to align with the rest of our <code>src/</code> tree. No GPL'd code anywhere in the lineage; Darling sources used only as reference for confirming behavior, not copied.</p>
<p><strong>What we deliver:</strong></p>
<ul>
<li><code>/usr/bin/hdiutil</code> — the CLI binary at Apple-canonical path</li>
<li><code>/usr/libexec/hdik</code> — the attach worker, if we want to mirror Apple's two-binary split</li>
<li><code>/usr/lib/libDiskImages.dylib</code> (or <code>.so.1</code>) — the format/compression library; private to our tools</li>
<li>Headers at <code>/usr/include/DiskImages/</code> for future Apple-shape consumers</li>
</ul>
<p><strong>Surface staged in phases</strong> (see §11 for the phased breakdown). Phase 1 = read-only <code>attach</code>/<code>detach</code> with UDZO/UDRO/ULFO and HFS+. Phase 2 = <code>info</code>/<code>imageinfo</code>/<code>verify</code>/<code>checksum</code>. Phase 3 = <code>create</code> and write support. Phase 4 = <code>convert</code> + sparse formats. Phase 5+ = the rest.</p>
<p><strong>Effort estimate:</strong></p>
<ul>
<li>Phase 1 alone: ~3–5K LOC. UDIF parser (~800 LOC), HFS+ read-only (~3K LOC), <code>md(4)</code> binding glue (~500 LOC), CLI argv + plist output (~500 LOC).</li>
<li>Phase 1 + 2 + 3 (read + write + create): ~10K LOC. Realistic 3–6 month effort at part-time iteration.</li>
<li>Full Apple coverage (all ~30 subcommands incl. APFS, sparse-bundle, encrypted, makehybrid, burn): much larger. Probably never — the long-tail subcommands matter only to a handful of workflows.</li>
</ul>
<p><strong>Tradeoffs:</strong></p>
<ul>
<li><span class="pill pill-good">+</span> License clean (MIT/BSD); aligns with Apple's no-GPLv3 posture (per rule 3, don't invent things Apple wouldn't ship)</li>
<li><span class="pill pill-good">+</span> Our code, our control; modifications stay re-licensable</li>
<li><span class="pill pill-good">+</span> Source lives in our <code>src/</code> tree; full vendoring story</li>
<li><span class="pill pill-bad">−</span> Largest write effort of the four options</li>
<li><span class="pill pill-bad">−</span> Slower path to first working <code>hdiutil attach</code> on the ISO (months, not weeks)</li>
<li><span class="pill pill-bad">−</span> Risk of subtle DMG/HFS+ parsing bugs not caught by our test set</li>
</ul>
</div>
<h2 id="option-b">6. Option B — Vendor <code>darling-dmg</code> as-is (GPL-3.0)</h2>
<div class="option-card">
<h3>Pull darling-dmg into <code>src/darling-dmg/</code>; ship as <code>/usr/bin/hdiutil</code></h3>
<p><strong>What it is:</strong> git-submodule or copy darling-dmg's source into our tree under <code>src/darling-dmg/</code>. Build it as-is against FreeBSD's <code>fusefs-libs</code>. Install the FUSE binary at <code>/usr/libexec/darling-dmg</code>; install the (rewritten) hdiutil shim at <code>/usr/bin/hdiutil</code>. License the vendored tree (and our shim) under GPL-3.0.</p>
<p><strong>What we deliver:</strong></p>
<ul>
<li><code>/usr/bin/hdiutil</code> — GPL-3.0 shim; ~150 LOC native BSD rewrite of Darling's <code>main-hdiutil.cpp</code></li>
<li><code>/usr/libexec/darling-dmg</code> — the FUSE backend binary, GPL-3.0</li>
<li><code>/usr/lib/libdmg.so</code> — GPL-3.0 library; only linked by the above two binaries</li>
</ul>
<p><strong>Surface delivered:</strong> ~7% of Apple's hdiutil. <code>attach</code> + <code>detach</code> only; read-only; UDIF formats only; HFS+/HFSX volumes only (no APFS).</p>
<p><strong>Effort estimate:</strong></p>
<ul>
<li>Initial vendoring + FreeBSD build wiring: ~1–2 weeks. Mostly Makefile/CMake glue + a ~150 LOC native hdiutil shim to replace the Darling-specific one (which needs <code><elfcalls.h></code> + the Mach-O/ELF bridge).</li>
<li>Per-test CI marker (PAM-style pattern): ~1 day.</li>
<li>Ongoing: bump submodule when upstream darling-dmg updates.</li>
</ul>
<p><strong>Tradeoffs:</strong></p>
<ul>
<li><span class="pill pill-good">+</span> Fastest path to a working <code>hdiutil attach</code> on the ISO — weeks not months</li>
<li><span class="pill pill-good">+</span> Source in our <code>src/</code> tree; full vendoring story</li>
<li><span class="pill pill-good">+</span> Tracks upstream improvements for free (when darling-dmg ships features, we get them)</li>
<li><span class="pill pill-warn">~</span> Apple-divergence: Apple doesn't ship GPLv3 code. They froze on <code>gcc 4.2.1</code> specifically to dodge the GPL-3.0 patent-grant clause; replaced GCC with clang/LLVM (Apache 2.0). Per rule 3, shipping GPLv3 inside an Apple-shape userland is knowingly diverging from Apple's licensing posture.</li>
<li><span class="pill pill-warn">~</span> Modifications stay GPL-3.0; can't later re-license fixes back to upstream Apple or to a BSD project</li>
<li><span class="pill pill-bad">−</span> 93% of Apple's subcommand surface still missing — consumers expecting <code>hdiutil create</code> / <code>convert</code> / <code>info</code> / etc. break</li>
<li><span class="pill pill-bad">−</span> <code>libdmg.so</code> must NEVER be linked into non-GPL binaries or those binaries become GPL-3.0; documented constraint with risk of accidental violation</li>
<li><span class="pill pill-bad">−</span> No write/create path ever, unless we move to Option C</li>
</ul>
</div>
<h2 id="option-c">7. Option C — Vendor <code>darling-dmg</code> + extend to full surface</h2>
<div class="option-card">
<h3>Start with darling-dmg, write the missing 93% on top</h3>
<p><strong>What it is:</strong> Option B as the starting point, then incrementally add the missing 28 subcommands (<code>create</code>, <code>convert</code>, <code>info</code>, <code>verify</code>, <code>compact</code>, <code>resize</code>, etc.) on top of darling-dmg's existing UDIF parser and HFS+ reader. Because GPL-3.0 is copyleft, our extensions also become GPL-3.0.</p>
<p><strong>What we deliver:</strong></p>
<ul>
<li>Everything from Option B, plus …</li>
<li>Incremental staged extensions per phase (see §11)</li>
<li>Full Apple-shape <code>hdiutil</code> over time, all GPL-3.0</li>
</ul>
<p><strong>Effort estimate:</strong> Phase 1 = Option B (1–2 weeks). Phase 2–5 broadly comparable to Option A's later phases (~6–12 months of part-time work to cover the breadth of <code>create</code>/<code>convert</code>/<code>compact</code>/etc.).</p>
<p><strong>Tradeoffs:</strong></p>
<ul>
<li><span class="pill pill-good">+</span> Fast first delivery (Option B's speed) + full eventual coverage (Option A's breadth)</li>
<li><span class="pill pill-good">+</span> Existing format/HFS+ parsers as foundation; saves the largest chunk of write effort</li>
<li><span class="pill pill-warn">~</span> Apple-divergence (same as Option B): GPL-3.0 in Apple-shape userland</li>
<li><span class="pill pill-warn">~</span> All our extensions inherit GPL-3.0 copyleft; permanent license commitment</li>
<li><span class="pill pill-bad">−</span> Tight coupling to darling-dmg upstream; their architectural choices (FUSE-only, read-only, single-threaded) constrain our extensions. Adding write support means significant refactoring of code we don't fully control.</li>
<li><span class="pill pill-bad">−</span> <code>libdmg.so</code> linking constraint as in Option B, but more painful: any helper binary we add (<code>hdik</code>, <code>diskimages-helper</code>) inherits GPL-3.0</li>
</ul>
</div>
<h2 id="option-d">8. Option D — Defer indefinitely</h2>
<div class="option-card">
<h3>Keep <code>mdconfig</code>/<code>mdmfs</code> from <code>/usr/src</code>; no <code>hdiutil</code> shipped</h3>
<p><strong>What it is:</strong> the current v3 plan position. Live ISO continues to use FreeBSD's <code>mdconfig</code>/<code>mdmfs</code> from the gap-filler manifest. No <code>hdiutil</code> binary is installed at Apple-canonical paths. DMG files have to be manually unpacked with third-party tools (e.g., <code>dmg2img</code> from pkg) if needed; Mac-source workflows that expect <code>hdiutil</code> just fail.</p>
<p><strong>What we deliver:</strong> nothing. Status quo.</p>
<p><strong>Effort estimate:</strong> zero.</p>
<p><strong>Tradeoffs:</strong></p>
<ul>
<li><span class="pill pill-good">+</span> Zero work; the v3 userland-cmds roadmap proceeds undisturbed</li>
<li><span class="pill pill-good">+</span> No license decision needed</li>
<li><span class="pill pill-good">+</span> No write effort wasted if hdiutil never becomes load-bearing for the project</li>
<li><span class="pill pill-bad">−</span> Anyone expecting Apple-shape <code>hdiutil</code> sees "command not found"</li>
<li><span class="pill pill-bad">−</span> Doesn't actually resolve the question — just postpones it</li>
<li><span class="pill pill-bad">−</span> Mismatch with the v3 "vendor everything we can from Apple" rule (this is the one Apple-shape userland gap that v3 deliberately leaves open)</li>
</ul>
</div>
<h2 id="decision-matrix">9. Decision matrix</h2>
<table>
<tr>
<th>Dimension</th>
<th>A: Clean-room</th>
<th>B: Vendor as-is</th>
<th>C: Vendor + extend</th>
<th>D: Defer</th>
</tr>
<tr>
<td>Time to first working <code>hdiutil attach</code></td>
<td>Months</td>
<td>1–2 weeks</td>
<td>1–2 weeks</td>
<td>Never</td>
</tr>
<tr>
<td>Eventual subcommand coverage</td>
<td>Phased; targetable to ~100%</td>
<td>~7% (forever, unless reclassified to C)</td>
<td>Phased; targetable to ~100%</td>
<td>0%</td>
</tr>
<tr>
<td>License</td>
<td>MIT/BSD-2</td>
<td>GPL-3.0</td>
<td>GPL-3.0</td>
<td>n/a</td>
</tr>
<tr>
<td>Apple-divergence (rule 3)</td>
<td>Aligned (no GPLv3, like Apple)</td>
<td>Divergent (Apple avoids GPLv3)</td>
<td>Divergent</td>
<td>Aligned by absence</td>
</tr>
<tr>
<td>Total write effort</td>
<td>~10K LOC + ongoing</td>
<td>~150 LOC shim</td>
<td>~150 LOC shim + ~10K LOC extensions</td>
<td>0</td>
</tr>
<tr>
<td>Linking-constraint risk</td>
<td>None</td>
<td><code>libdmg.so</code> can't be linked into non-GPL binaries</td>
<td>Same; more surface to police</td>
<td>None</td>
</tr>
<tr>
<td>Re-licensability of our work</td>
<td>Full</td>
<td>None (vendored is GPL-3.0)</td>
<td>None (extensions inherit GPL-3.0)</td>
<td>n/a</td>
</tr>
<tr>
<td>APFS support path</td>
<td>Phase 5+; we control it</td>
<td>Never (darling-dmg has no APFS)</td>
<td>Future GPL-3.0 work</td>
<td>Never</td>
</tr>
<tr>
<td>Write/create support</td>
<td>Phase 3+</td>
<td>Never</td>
<td>Future GPL-3.0 work</td>
<td>Never</td>
</tr>
</table>
<h2 id="cross-cutting">10. Cross-cutting concerns</h2>
<h3>10.1. GPL-3.0 in FreeBSD/Apple-shape userland — is it actually a problem?</h3>
<p><strong>FreeBSD precedent:</strong> the base system has shipped GPLv2 (gcc 4.2.1, groff, some diff tools) for decades. GDB was in base and was <strong>GPLv3</strong> at one point. The "BSD only" framing is aspirational, not absolute; FreeBSD does ship GPLv3 components when there's no alternative.</p>
<p><strong>Apple precedent:</strong> Apple <em>explicitly</em> avoids GPLv3. They froze on <code>gcc 4.2.1</code> — the last GPLv2 release — rather than upgrade to a GPLv3 gcc. They eventually replaced GCC entirely with clang/LLVM (Apache 2.0). They also avoid GPLv3 versions of bash (frozen on bash 3.2), readline, etc. The reasons are widely understood to be the GPLv3 patent-grant clause and the anti-tivoization clause.</p>
<p><strong>Net read for this project:</strong> shipping GPLv3 inside an otherwise Apple-shape userland is a knowingly-divergent choice. It's not a legal block, but it's a posture mismatch. Per rule 3 ("don't invent things Apple doesn't do"), the lean is toward avoiding GPL-3.0 if there's a reasonable alternative — and Option A is a reasonable alternative. The question is whether the time-to-delivery benefit of Options B/C outweighs the posture mismatch.</p>
<h3>10.2. The <code>libdmg.so</code> linking constraint (Options B and C)</h3>
<p>GPL-3.0 is copyleft. Any binary that links (statically or dynamically) against <code>libdmg.so</code> is "based on" GPL-3.0 work and itself becomes subject to GPL-3.0 distribution requirements (source must be made available, etc.). Practically this means: only <code>hdiutil</code> and <code>hdik</code> binaries (which we're OK with being GPL-3.0) can link <code>libdmg.so</code>. Any other tool that wants DMG-parsing capability would have to either (a) become GPL-3.0 too, or (b) shell out to <code>hdiutil</code> rather than linking.</p>
<p>This is a permanent operational discipline. Easy to enforce with a CI check (<code>ldd</code> on each non-GPL binary, fail if <code>libdmg.so</code> appears). But it's a permanent constraint that doesn't exist under Option A.</p>
<h3>10.3. <code>hdik</code> as a separate binary</h3>
<p>On Apple, <code>hdik</code> is the lower-level attach worker that <code>hdiutil</code> shells to. Under our options:</p>
<ul>
<li><strong>Option A:</strong> we can mirror Apple's two-binary split if we want Apple-shape process topology, or skip it and have hdiutil do everything in-process. Pure architectural choice.</li>
<li><strong>Option B:</strong> Darling never built <code>hdik</code>; we'd have to write a stub or skip it. <code>hdiutil</code> just calls into <code>libdmg.so</code> directly.</li>
<li><strong>Option C:</strong> same as B; <code>hdik</code> can be written as a thin GPL-3.0 wrapper if Apple-shape topology is wanted.</li>
<li><strong>Option D:</strong> n/a.</li>
</ul>
<h3>10.4. APFS</h3>
<p>Modern Mac DMGs (post-macOS 10.13) often contain APFS volumes, not HFS+. APFS is open-spec but Apple's reference implementation is closed. Third-party readers exist (libapfs, apfs-fuse) but coverage is partial and quality varies.</p>
<table>
<tr><th>Option</th><th>APFS path</th></tr>
<tr><td>A</td><td>Add APFS reader as a later phase; pick from MIT-compatible libraries or write our own.</td></tr>
<tr><td>B</td><td>No APFS, ever — darling-dmg has none.</td></tr>
<tr><td>C</td><td>Add APFS as GPL-3.0 extension; could vendor <code>apfs-fuse</code> (GPL-2 or GPL-3) under our broader GPL-3 envelope.</td></tr>
<tr><td>D</td><td>n/a.</td></tr>
</table>
<h3>10.5. <code>md(4)</code> binding for synthetic <code>/dev/disk*</code></h3>
<p>Apple's <code>hdiutil attach</code> creates a <code>/dev/disk*</code> node via <code>IOHDIXController</code>. Our equivalent under any option is binding to FreeBSD's <code>md(4)</code> — vnode-backed memory disk creates <code>/dev/md*</code>. We'd either (a) symlink or alias <code>/dev/disk*</code> → <code>/dev/md*</code> for Apple-shape paths, or (b) name disagree with Apple and accept it. This is independent of the option chosen above; it's the actual attach mechanism.</p>
<p>For the FUSE-based Options B and C: no <code>/dev/disk*</code> at all — the mount target is a normal FUSE mountpoint. Apps that look for <code>/dev/disk*</code> won't find it. This is a structural divergence from Apple's model that darling-dmg accepts; we'd inherit it.</p>
<h2 id="staged-scope">11. Staged scope (if Option A or C selected)</h2>
<p>Phasing for incremental delivery. Each phase ships independently with its own CI marker.</p>
<table>
<tr><th>Phase</th><th>What lands</th><th>CI marker</th><th>Apple coverage gain</th></tr>
<tr>
<td>1</td>
<td><code>attach</code> + <code>detach</code> for UDZO/UDRO/ULFO + HFS+; read-only</td>
<td><code>HDIUTIL-ATTACH-OK</code></td>
<td>~7% (matches darling-dmg)</td>
</tr>
<tr>
<td>2</td>
<td><code>info</code>, <code>imageinfo</code>, <code>checksum</code>, <code>verify</code>, <code>pmap</code>, <code>isencrypted</code>, <code>plugins</code></td>
<td><code>HDIUTIL-INFO-OK</code></td>
<td>~25%</td>
</tr>
<tr>
<td>3</td>
<td><code>create</code> (sparse + sparse-bundle), write support to HFS+ filesystem creation</td>
<td><code>HDIUTIL-CREATE-OK</code></td>
<td>~45%</td>
</tr>
<tr>
<td>4</td>
<td><code>convert</code> (UDIF↔sparse↔sparse-bundle), <code>compact</code>, <code>resize</code>, <code>segment</code></td>
<td><code>HDIUTIL-CONVERT-OK</code></td>
<td>~65%</td>
</tr>
<tr>
<td>5</td>
<td>APFS reader (mount-only first; write later if needed)</td>
<td><code>HDIUTIL-APFS-OK</code></td>
<td>~75%</td>
</tr>
<tr>
<td>6</td>
<td>Encryption (<code>chpass</code>, attach with password, sparse-encrypted)</td>
<td><code>HDIUTIL-ENCRYPT-OK</code></td>
<td>~85%</td>
</tr>
<tr>
<td>7+</td>
<td>Long tail: <code>burn</code>, <code>internet-enable</code>, <code>makehybrid</code>, <code>udifrez</code>/<code>udifderez</code>, NDIF read, …</td>
<td>(per-feature)</td>
<td>~100%</td>
</tr>
</table>
<p>Realistic mileage: phases 1–3 are the load-bearing feature set for any actual ISO-production workflow. Phases 4–6 cover the daily-driver "I downloaded a DMG" experience. Phase 7+ is optional polish that may never need shipping.</p>
<p class="footnote">Drafted 2026-05-26. Companion to the <a href="freebsd-apple-userland-cmds-plan.html">freebsd-apple-userland-cmds v3 plan §16 deferred scoping</a>. Source data: two parallel research agent passes against <code>darlinghq/darling-dmg</code> and <code>darlinghq/darling</code> source trees, plus reference to Apple's <code>hdiutil(1)</code> man page and Technote TN1150. Decision pending in §9.</p>
</div>
</body>
</html>