Porting Apple's HFS+ filesystem as a vendored FreeBSD hfs.ko kernel module + userland tools (mount_hfs, newfs_hfs, fsck_hfs), targeting read-write mount, journal support, and decmpfs decompression. Scopes issue #73 (HFS+ support scoping). The headline find: a GSoC 2025 port (stupendoussuperpowers/freebsd_hfs, FreeBSD Foundation mentored) already exists with RO+RW mount + basic VOPs working; the port stopped at journal + fsck/newfs + decmpfs. We fork that as the starting point.
Why this matters:
.dmg Apple distributes contains an HFS+ volume; with hfs.ko + md(4) + a DMG extractor we mount installer images without FUSE.mount_hfs / newfs_hfs / fsck_hfs from apple-oss-distributions/hfs — same lineage as the modern XNU HFS+ tools, matching Mac behavior exactly.darling-dmg dependency for the read path — darling-dmg embeds its own HFS+ reader, which becomes redundant once we have kernel-side HFS+.mount_hfs, newfs_hfs, fsck_hfs)HFS+ is Apple's pre-APFS filesystem and remains the on-disk format inside almost every .dmg Apple distributes (installers, source-release archives from apple-oss-distributions, Mac OS X 10.4–10.12 boot drives, Time Machine backups against non-APFS targets). Without HFS+ support freebsd-launchd-mach can't:
.dmg files we vendor fromfreebsd-launchd-mach machineCurrent state in the wider FreeBSD ecosystem: sysutils/fusefs-hfsfuse (MIT) provides solid read-only HFS+ via FUSE — including journal-aware reads, decmpfs decompression, hard links, xattrs. ravynOS uses this combination (fusefs-hfsfuse + dmg2img) and it works. The question this plan answers: do we want better than that? Read-write, in-kernel, Apple-source-aligned. Yes — matches the project's overall "port Apple userland from Apple source" stance.
| Source | License | Read | Write | Journal | decmpfs | Notes |
|---|---|---|---|---|---|---|
apple-oss-distributions/hfs | APSL 2.0 | Yes | Yes | Yes | References XNU's; not bundled | The reference implementation; ~65-75K SLOC kernel, ~30K SLOC userland. Targets XNU. |
Linux fs/hfsplus/ | GPL-2.0 | Yes | Non-journaled only | No (silently downgrades RO with warning) | No | ~27 source files. Not actively maintained since 2008. License-incompatible with BSD base kmod. |
NetBSD sys/fs/hfs/ | BSD-2-Clause | Yes (RO only) | No | No | No | Clean-room reimplementation. ~160 KB C. "MASSIVE performance + memory optimization pending" per its own TODO. |
0x09/hfsfuse (FreeBSD port: sysutils/fusefs-hfsfuse) | MIT | Yes | No | Read-aware | zlib + LZVN + LZFSE | Extends NetBSD libhfs. Most capable FUSE option. Already in FreeBSD ports. |
darling-dmg | GPL-3.0 | Yes | No | No | No | FUSE-based, HFS+ embedded in DMG. Library-usable. |
stupendoussuperpowers/freebsd_hfs | APSL 2.0 (inherited from Apple) | Yes | Yes (basic) | Claimed but unverified | No | GSoC 2025, FreeBSD Foundation. Fork of Apple's HFS + compat shim. Parked July 2025. |
johnothwolo/hfs-freebsd | MPL-2.0 | Yes (RO) | No (explicitly disabled) | No | No | Earlier WIP, less complete than the GSoC port. Based on Apple HFS 556.60.1. |
The GSoC port (row 6) is the most-advanced FreeBSD-kernel target we can build on. APSL 2.0 license aligns with the rest of our vendored Apple sources.
Project: stupendoussuperpowers/freebsd_hfs by Sanchit Sahay (NYU), FreeBSD Foundation-mentored. Targets FreeBSD 14. Last commit 2025-07-03. Status documented in the FreeBSD Q2 2025 status report as "successfully completed" for the GSoC milestone scope, though clearly more work remains.
What works (per repo README + blog post):
ls, stat, mkdir, touch, cat, echo > filemount_hfs binary in disk_bin/What's missing:
mmap — needed for shared-memory binaries; current code uses vn_read/vn_write onlyhfs_journal.c is 157 KBhfscommon/ (still uses Apple shims rather than FreeBSD-native impls)newfs_hfs (Rust stub only; no real backend)fsck_hfs (Rust stub only; no real backend)Tree layout (model for our fork):
xnu/ — verbatim Apple HFS source from apple-oss-distributions/hfshfsplus/ — HFS logic + hfscommonvfs/ — UTF-8 utilities from XNUsys/ — compat shims (aliased types, header bridges) — the FreeBSD/XNU impedance layerdisk_bin/ — Rust userland scaffolding for mount/newfs/fsckSpecific FreeBSD VFS porting deltas Sahay documented:
#ifdef DARWIN_QUOTAconsole_user removedg_vfs_open, g_vfs_close, consumer/provider pattern)insmntqueue() adoptionbuf_obj_ops implementationINVARIANTS + WITNESS + VFS_DEBUG_LOCKSThese deltas are the "owed work" any HFS+→FreeBSD port has to pay; Sahay paid most of it. Forking from him means we inherit ~3 engineer-months of saved work.
From apple-oss-distributions/hfs top-level (research-verified):
| Directory | Bytes | Purpose |
|---|---|---|
core/ | 2.5 MB | Kernel-side HFS+ filesystem (the kext). ~65-75K SLOC. |
livefiles_hfs_plugin/ | 1.9 MB | Userland HFS+ implementation (UserFS/livefiles plugin) — a second, parallel impl. |
lib_fsck_hfs/ + dfalib/ | 1.5 MB | fsck engine ("dfa" = disk first aid). Mostly self-contained C. |
newfs_hfs/ | 130 KB | mkfs.hfs equivalent. |
hfs_util/ | 176 KB | hfs.util — Disk Arbitration helper (probe/mount/fsck/getuuid). DA-dependent. |
fsck_hfs/ | 99 KB | fsck driver/CLI shell over lib_fsck_hfs. |
CopyHFSMeta/ | 66 KB | Metadata-only volume copier (diagnostics). |
mount_hfs/ | 39 KB | mount_hfs(8) — the most portable tool. |
hfs_encodings/ | 32 KB | Legacy MacRoman/non-Unicode encoding tables (separate kext). |
Modern HFS+ features in the source:
core/hfs_journal.c 157 KB, full transaction-log impl.hfs_readwrite.c but impl lives in XNU (bsd/kern/decmpfs.c ~65 KB). Must be separately vendored.hfs_xattr.c 97 KB, backed by the Attributes B-tree.hfs_link.c 40 KB, both file (indirect-node) and dir (Time Machine) variants.hfs_hotfiles.c 106 KB.hfs_cprotect.c 77 KB. Requires Apple AppleKeyStore kext; stubbable to no-op for non-encrypted volumes, but cannot decrypt encrypted volumes.hfs_notification.c calls XNU's fsevents (different from FreeBSD's kqueue VNODE).resize, attrlist, fsctl, search — all present.Things that need bridging through a compat layer (the sys/ directory in the GSoC port; conceptually equivalent to OpenZFS's SPL):
| Apple primitive | FreeBSD equivalent | Bridging approach |
|---|---|---|
UBC/cluster API: cluster_read/write/pageout, ubc_setsize/getsize, ubc_upl_* | vnode_pager_*, vop_getpages/putpages | Translation layer in hfs_readwrite.c. Route file I/O through vn_read/vn_write + bufobj. This is the largest single bridge. |
vfs_context_t (cred+thread bundled) | Separate struct ucred * + struct thread * | Mechanical but pervasive find/replace. |
vnode_t opaque accessors (vnode_iocount, vnode_put, vnode_create, …) | struct vnode * direct access (vref/vrele/VOP_* macros) | Wrap with macros: #define vnode_put(vp) vrele(vp). |
buf_t accessors | struct buf * with field access | Wrapper macros. |
kauth_cred_t | struct ucred * | Wrap with the ~10 kauth_cred_get* accessors HFS actually uses. |
lck_mtx_t, lck_rw_t, lck_spin_t | struct mtx, struct sx (or rwlock), MTX_SPIN | Compat header. Init API differs (lck_grp_alloc_init vs mtx_init). |
OSMalloc, OSKextLib, pexpert | malloc(M_HFS, …), FreeBSD module init | Drop; replace. |
thread_call_* (deferred work) | taskqueue or callout | Direct equivalent. |
IOKit/IOMedia content hints | GEOM probing | Drop hfs_iokit.cpp entirely. |
fsevents (add_fsevent) | (none — FreeBSD uses kqueue VNODE) | Stub to no-op; FreeBSD kqueue VNODE notifications come "for free" via VFS. |
KAUTH_* listeners | (none) | Stub. |
<sys/disk.h> ioctls (DKIOC*) | GEOM g_io_getattr | Translate to GEOM. Already done in Sahay's port. |
| AppleKeyStore / cprotect crypto | (none) | Cannot port. Compile out behind #ifndef HFS_CONTENT_PROTECTION. Document as limitation. |
decmpfs.c | (none — must vendor from XNU) | Pull bsd/kern/decmpfs.c (~65 KB) into our tree. Otherwise compressed files read as zero-length. Hard requirement. |
Decision: fork stupendoussuperpowers/freebsd_hfs into our tree at src/hfs/. Do not start fresh; do not start from Linux hfsplus.ko (GPL-2 incompatible); do not start from NetBSD's libhfs (too incomplete to bridge to RW + journal).
Rationale:
sys/ compat-shim pattern maps 1:1 to OpenZFS's SPL pattern, which has a decade-plus of proven success on FreeBSD.What forking means here: import Sahay's tree wholesale into freebsd-launchd-mach's src/hfs/, treat it as a vendor branch we own. Rebase periodically against apple-oss-distributions/hfs main (Sahay's xnu/ directory tracks Apple's source). Stay on Sahay's git history as upstream until/unless he restarts — in which case we contribute back.
| Phase | Effort | Deliverable | CI marker |
|---|---|---|---|
| 1 | 1–2 weeks | Vendor freebsd_hfs at src/hfs/. Get the kext loading + RO mounting an HFS+ image in QEMU CI. Land mount_hfs(8) stub. |
HFSKMOD-MOUNT-RO-OK |
| 2 | 2–3 weeks | RW path validated. Validate Sahay's write code against real Apple-formatted images. Port real newfs_hfs (replace Rust stub). |
HFSKMOD-MOUNT-RW-OK, HFSKMOD-NEWFS-OK |
| 3 | 3–4 weeks | Port fsck_hfs userland. Userland-only, mostly portable. lib_fsck_hfs/dfalib/ is 1.35 MB of standalone-ish C. Killer-app deliverable for DMG forensics even without journal RW. |
HFSKMOD-FSCK-OK |
| 4 | 4–6 weeks | Journal support. The hardest part. hfs_journal.c is 157 KB, deeply integrated with XNU's buf_t + IO barriers. Without this, RW mounts of journaled volumes (default on every modern Mac) require disabling the journal first. |
HFSKMOD-JOURNAL-OK |
| 5 | 2–3 weeks | decmpfs. Vendor bsd/kern/decmpfs.c from XNU. Wire to FreeBSD UBC equivalents. Without it, virtually every modern macOS system binary reads as 0 bytes. |
HFSKMOD-DECMPFS-OK |
| 6 | 1–2 weeks | mmap support via FreeBSD vnode_pager. Required for shared-memory binaries. |
HFSKMOD-MMAP-OK |
| 7+ | Open | Optional: content-protection stub, attrlist polish, fsevents stub, hot-file, hfs_japanese, NFS export, root-fs. | (per-feature) |
Total realistic effort: ~3–4 engineer-months for Phases 1–6. The "read-write, journaled, decompresses, fsckable" milestone is the proper-product threshold.
MVP shortcut: Phases 1 + 3 + 5 alone (RO mount + fsck + decmpfs) makes Apple DMGs and installer images natively mountable on our FreeBSD — that's the headline benefit even before RW lands. ~6–9 weeks total.
mount_hfs, newfs_hfs, fsck_hfs)Apple's open-source HFS repo ships userland tools alongside the kext. Per the Apple README: "the other HFS targets are known not to build outside of Apple's internal build environment." Tooling depends on:
libutil (BSD-compat, mostly portable)<sys/disk.h> ioctls (DKIOCGETBLOCKSIZE vs FreeBSD's DIOCGSECTORSIZE) — translateEstimated per-tool porting effort:
| Tool | Apple LOC | FreeBSD port effort | Notes |
|---|---|---|---|
mount_hfs(8) | 39 KB | 1 week | Calls mount(2). Most portable. |
newfs_hfs(8) | 130 KB | 2 weeks | Uses BSD-ish raw-device IO. Translation of disk-ioctls. |
fsck_hfs(8) + lib_fsck_hfs/dfalib/ | 1.45 MB | 3–4 weeks | Mostly self-contained C. Biggest payoff post-RO-mount. |
hfs.util | 176 KB | (defer) | Depends on Apple's diskarbitrationd. We're porting DA separately; this can come later. |
CopyHFSMeta | 66 KB | 1 week if wanted | Diagnostic tool. Optional. |
fstyp_hfs | 7 KB | 1 day | Trivial. |
No Q3/Q4 2025 follow-up. We own rebases against FreeBSD-current as kernel APIs drift. Mitigation: start from Sahay's last good commit, treat as a vendor fork, contribute back if Sahay restarts.
hfs_journal.c integrates with XNU IO barriers and buf_t semantics in ways FreeBSD's bufobj doesn't directly match. There's a reason Sahay stopped here. A r/w journal port that lies about successful replay silently corrupts the volume. Mitigation: extensive soak testing on dirty-journal images generated by real macOS; degrade to RO if journal-replay confidence is low.
APSL 2.0 is OSI-approved free software but not GPL-compatible. For our project this is fine; for FreeBSD upstream this would matter. Document clearly in kmod LICENSE. Same posture as the rest of our vendored Apple source.
Modern macOS volumes have most /System/Library/… files decmpfs-compressed. A driver without LZVN/LZFSE/zlib decode silently reads zeros for the file body. Apple's decompressor licenses are usable (LZFSE is BSD-3, LZVN is more constrained, zlib is zlib license). Each adds an in-kernel dependency.
Apple's vnode_create/vnode_recycle/vnode_iterate patterns differ from FreeBSD's vrele/vput/vunref conventions. INVARIANTS + WITNESS + VFS_DEBUG_LOCKS will surface bugs early (Sahay already leans on these), but the long tail of cache_lookup / cache_enter_time rules will regress under load. NTFS and ext2's r/w-edge history both confirm this; production-quality RW takes years of soak. Mitigation: ship Phase 2 as opt-in, default to RO; gather field data before declaring RW production.
XNU buf_t exposes buf_meta_bread, buf_setflags(B_LOCKED), vectored cluster_* I/O, and vnode_pager_setsize semantics that don't have 1:1 FreeBSD analogs. HFS's hfs_systemfile_* puts B-trees in metadata buffers — lock-order inversion against bufobj is easy. Mitigation: methodical conversion module-by-module, lean on WITNESS to catch inversions during boot.
Cannot port — Apple's HSM-backed key wrapping (Data Protection classes A–F) requires AppleKeyStore. Document as limitation; encrypted volumes won't open. Stub hfs_cprotect.c to ENOTSUP.
| Workstream | Relationship | Status |
|---|---|---|
| Bootloader for HFS+ (#80 — "Scope Apple boot-132") | FreeBSD's existing /boot/loader can't read HFS+. Booting from HFS+ requires either: (a) porting Apple's boot-132 (or a modern derivative like OpenCore); (b) adding HFS+ to FreeBSD's loader's filesystem support. Separate scoping plan in flight (research dispatched 2026-05-26). |
Companion plan |
| DMG utilities (#74, hdiutil port plan) | DMG mounting combines DMG-container parsing + filesystem (HFS+) reader. Our HFS+ kmod unlocks the FS side; the DMG-container side is separately scoped in the hdiutil plan (four options for that workstream). | Companion plan |
| APFS | macOS post-10.13 defaults to APFS. Closed-source on Apple side. Third-party readers exist (apfs-fuse GPL, libapfs partial). Different workstream entirely; HFS+ doesn't unlock APFS. |
Separate effort (deferred) |
| DiskArbitration port (existing plan) | Apple's hfs.util depends on diskarbitrationd. DA port is independently scoped; once it lands we can port hfs.util as a follow-up. |
Existing plan |
livefiles_hfs_plugin/ — Apple's userland HFS+ reimplementation for UserFS/livefiles. Interesting reference but we don't need a second userland impl; kmod is the goal.hfs_japanese/ — JIS encoding tables. Niche; skip until/unless a real consumer asks.hfs_appex/ — iOS UserFS app extension. Apple-only.Drafted 2026-05-26 from two parallel agent research passes: (1) Apple HFS source deep dive at apple-oss-distributions/hfs; (2) existing FreeBSD/Linux/NetBSD HFS+ ports + FreeBSD VFS bridging precedents. Companion to hdiutil port plan. Scopes issue #73. Key references: apple-oss-distributions/hfs, stupendoussuperpowers/freebsd_hfs, FreeBSD Q2 2025 status report, hfsfuse, Apple TN1150.