HFS+ kernel module port plan for FreeBSD

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:

Contents

  1. Why HFS+
  2. Source landscape
  3. The GSoC 2025 FreeBSD port (Sahay)
  4. Apple's HFS source — what it contains
  5. Apple/FreeBSD VFS impedance points
  6. Strategy: fork freebsd_hfs as starting point
  7. Phased delivery
  8. Userland tools (mount_hfs, newfs_hfs, fsck_hfs)
  9. Risks
  10. Bootloader + DMG + APFS — related work
  11. Out of scope

1. Why 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:

Current 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.

2. Source landscape

SourceLicenseReadWriteJournaldecmpfsNotes
apple-oss-distributions/hfsAPSL 2.0YesYesYesReferences XNU's; not bundledThe reference implementation; ~65-75K SLOC kernel, ~30K SLOC userland. Targets XNU.
Linux fs/hfsplus/GPL-2.0YesNon-journaled onlyNo (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-ClauseYes (RO only)NoNoNoClean-room reimplementation. ~160 KB C. "MASSIVE performance + memory optimization pending" per its own TODO.
0x09/hfsfuse (FreeBSD port: sysutils/fusefs-hfsfuse)MITYesNoRead-awarezlib + LZVN + LZFSEExtends NetBSD libhfs. Most capable FUSE option. Already in FreeBSD ports.
darling-dmgGPL-3.0YesNoNoNoFUSE-based, HFS+ embedded in DMG. Library-usable.
stupendoussuperpowers/freebsd_hfsAPSL 2.0 (inherited from Apple)YesYes (basic)Claimed but unverifiedNoGSoC 2025, FreeBSD Foundation. Fork of Apple's HFS + compat shim. Parked July 2025.
johnothwolo/hfs-freebsdMPL-2.0Yes (RO)No (explicitly disabled)NoNoEarlier 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.

3. The GSoC 2025 FreeBSD port (Sahay)

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):

What's missing:

Tree layout (model for our fork):

Specific FreeBSD VFS porting deltas Sahay documented:

These 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.

4. Apple's HFS source — what it contains

From apple-oss-distributions/hfs top-level (research-verified):

DirectoryBytesPurpose
core/2.5 MBKernel-side HFS+ filesystem (the kext). ~65-75K SLOC.
livefiles_hfs_plugin/1.9 MBUserland HFS+ implementation (UserFS/livefiles plugin) — a second, parallel impl.
lib_fsck_hfs/ + dfalib/1.5 MBfsck engine ("dfa" = disk first aid). Mostly self-contained C.
newfs_hfs/130 KBmkfs.hfs equivalent.
hfs_util/176 KBhfs.util — Disk Arbitration helper (probe/mount/fsck/getuuid). DA-dependent.
fsck_hfs/99 KBfsck driver/CLI shell over lib_fsck_hfs.
CopyHFSMeta/66 KBMetadata-only volume copier (diagnostics).
mount_hfs/39 KBmount_hfs(8) — the most portable tool.
hfs_encodings/32 KBLegacy MacRoman/non-Unicode encoding tables (separate kext).

Modern HFS+ features in the source:

5. Apple/FreeBSD VFS impedance points

Things that need bridging through a compat layer (the sys/ directory in the GSoC port; conceptually equivalent to OpenZFS's SPL):

Apple primitiveFreeBSD equivalentBridging approach
UBC/cluster API: cluster_read/write/pageout, ubc_setsize/getsize, ubc_upl_*vnode_pager_*, vop_getpages/putpagesTranslation 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 accessorsstruct buf * with field accessWrapper macros.
kauth_cred_tstruct ucred *Wrap with the ~10 kauth_cred_get* accessors HFS actually uses.
lck_mtx_t, lck_rw_t, lck_spin_tstruct mtx, struct sx (or rwlock), MTX_SPINCompat header. Init API differs (lck_grp_alloc_init vs mtx_init).
OSMalloc, OSKextLib, pexpertmalloc(M_HFS, …), FreeBSD module initDrop; replace.
thread_call_* (deferred work)taskqueue or calloutDirect equivalent.
IOKit/IOMedia content hintsGEOM probingDrop 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_getattrTranslate 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.

6. Strategy: fork freebsd_hfs as starting point

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:

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.

7. Phased delivery

PhaseEffortDeliverableCI 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.

8. Userland tools (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:

Estimated per-tool porting effort:

ToolApple LOCFreeBSD port effortNotes
mount_hfs(8)39 KB1 weekCalls mount(2). Most portable.
newfs_hfs(8)130 KB2 weeksUses BSD-ish raw-device IO. Translation of disk-ioctls.
fsck_hfs(8) + lib_fsck_hfs/dfalib/1.45 MB3–4 weeksMostly self-contained C. Biggest payoff post-RO-mount.
hfs.util176 KB(defer)Depends on Apple's diskarbitrationd. We're porting DA separately; this can come later.
CopyHFSMeta66 KB1 week if wantedDiagnostic tool. Optional.
fstyp_hfs7 KB1 dayTrivial.

9. Risks

9.1. GSoC port is parked

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.

9.2. Journal port is genuinely hard

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.

9.3. APSL 2.0 license posture

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.

9.4. decmpfs is a hard requirement, not optional

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.

9.5. Vnode lifecycle + namecache races

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.

9.6. Buffer cache impedance

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.

9.7. Content protection (per-file encryption)

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.

WorkstreamRelationshipStatus
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

11. Out of scope

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.