NextBSD · Research · Alternate path

HFS+ on NextBSD — porting Apple's open filesystem

An alternate research path to the ZFS/SMB stack: adopt HFS+ as NextBSD's Mac-native filesystem — the authentic backing for a future AFP server and the old-Mac aesthetic — by finishing the freebsd_hfs port of Apple's fully-open HFS source into nextbsd-kernel, with the matching Apple utilities in nextbsd-userland.

Scoped 2026-07-16 from three source-grounded agents (Apple HFS OSS inventory · nextbsd-kernel integration · nextbsd-userland tooling). This does not supersede the main directory plan (shared by both tracks) — it's a parallel filesystem/sharing track kept alive alongside it. Planning only — no code written.

Scope update — 2026-07-16 (read this first) The direction has settled and this doc's original framing (HFS+ as an alternate default filesystem, eventually replacing UFS) is superseded. The chosen path is UFS as the filesystem (the live ISO stays UFS for sure) with the case-insensitivity patch + a native AFP server, serving the real goal — DirectoryServices network users. HFS+ is now a footnote: worth revisiting only as read-only disk interop — mounting real HFS+ disks/.dmg/Time Machine to migrate Mac data into NextBSD — and likely moot once UFS is patched. It is not a default or alternate root filesystem. (ZFS is also not dropped — it just won't be the live-ISO filesystem; it remains a supported installed-system option.) The detailed port research below is retained as reference for the read-only-interop case; ignore the "alternate main path / replace UFS" framing.

Verdict

Viable, and cleaner than it first looks — because the whole HFS+ stack is already open. Every piece (kernel filesystem and tools) is APSL-2.0 in one Apple repo, so the work isn't inventing anything — it's finishing the port of code that already exists. But it's a real, staged effort with a hard core (rw + journaling correctness) and two NextBSD-specific sharp edges.

1Where this sits — the alternate track

This is Path 2 of two parallel research directions; nothing here retracts Path 1.

LayerPath 1 — main (documented)Path 2 — this doc alternate
FilesystemZFSHFS+ (Mac-native)
File sharingillumos smbsrv (SMB)own AFP server (HFS+ native forks)
Directory / logindirectoryservicesdshared by both; independent of the FS choice

They diverge only at the filesystem + sharing layer and converge on the directory. The proposed sequencing for this track: HFS+ → own AFP server → DirectoryServices, with DirectoryServices run as a parallel track (it doesn't depend on HFS+/AFP), and HFS+ kept off the boot path (UFS root) until it's corruption-clean.


2Apple's HFS+ is fully open — one repo, APSL 2.0

Answer to "is all the HFS code open?" Yes — the entire stack, kernel + tools, is APSL 2.0 in apple-oss-distributions/hfs (latest hfs-715.120.4, June 2026). Nothing is closed or absent. (xnu no longer carries bsd/hfs — it was split into this standalone repo; diskdev_cmds no longer ships the HFS tools either. The hfs repo is now canonical for everything.)
ComponentIn apple-oss-distributions/hfsFeature-complete?
Kernel filesystemcore/ — journaling (hfs_journal.c), catalog + extents B-trees, resource/named forks, xattrs + ACLs (hfs_xattr.c), HFSX case-sensitive + case-insensitive, per-file encryption✅ full
newfs_hfs (mkfs)newfs_hfs/
fsck_hfs + lib_fsck_hfsfsck_hfs/✅ (self-contained B-tree checker, no CoreFoundation)
mount_hfsmount_hfs/
hfs.util (diskarb/mount helper)hfs_util/✅ (CoreFoundation-dependent)
ExtrasCopyHFSMeta, fstyp_hfs, livefiles_hfs_plugin, …

The strategic consequence: the "missing" features in freebsd_hfs aren't gaps to invent — they're Apple code not yet ported. That turns the effort from "write a filesystem" into "finish a port," which is materially less risky (the on-disk-format and algorithm correctness already exist and are battle-tested).


3The starting point — freebsd_hfs

github.com/stupendoussuperpowers/freebsd_hfs — a direct port of Apple's hfs/core to FreeBSD 14

State
WorksRead path: ACCESS/OPEN/CLOSE/READDIR/READ/READLINK/LOOKUP; ls, symlinks, ./.. correct. Write path started: VOP_CREATE (mkdir/touch), VOP_WRITE.
Missing (the port-to-finish)journaling (TODO — rw is corruption-prone without it) · newfs_hfs/fsck_hfs (stubs) · mmap (EINVAL) · xattrs / resource forks / ACLs · case-insensitive vs HFSX handling · seek/truncate untested · macOS compat shims still present
ProvenancePorts Apple's core/hfscommon tree → APSL 2.0 by inheritance. Clean derivation, but the repo declares no top-level license — add an explicit APSL-2.0 LICENSE/attribution before building on it.
ActivitySingle-author PoC; last commit 2025-07-02 ("Write support start"); "builds on work previously done on FreeBSD 5."

So it's roughly Apple's read path + a nascent write path, minus journaling, tooling, and advanced metadata — a real head start on the mechanical VFS glue, with the hard correctness work (journaling, rw) still ahead. All of it is a porting job against Apple's complete source, not new design.


4Kernel integration — nextbsd-kernel

The repo model (verified)

nextbsd-kernel is not a fork of freebsd-src — it's a thin overlay applied over a baked /usr/src (releng/15.1): patches/*.patch (edits to freebsd files) + config/NEXTBSD (kernel config) + src-overlay/ (NextBSD-original sources copied in, with conf/files.* fragments appended). The custom vfs_pivot.c is the exact precedent for landing a new static subsystem — and HFS+ follows it almost verbatim.

Where HFS+ lands (no fork patch)

# thousands of NEW files → src-overlay, NOT patches/
src-overlay/sys/fs/hfs/**          # driver sources + private/format headers
src-overlay/conf/files.hfs         # the sys/conf/files fragment:
      fs/hfs/hfs_vfsops.c   optional hfs
      fs/hfs/hfs_vnops.c    optional hfs
      fs/hfs/hfs_journal.c  optional hfs
      ...                   optional hfs
config/NEXTBSD                     # add:  options HFS
.github/workflows/build.yml        # new "Wire in HFS" step (clone of the vfs_pivot step):
      cat src-overlay/conf/files.hfs >> /usr/src/sys/conf/files
      printf 'HFS\topt_hfs.h\n'      >> /usr/src/sys/conf/options

Registration is standard static-FS: VFS_SET(hfs_vfsops, hfs, 0)vfs_register at SYSINIT, mount -t hfsvfs_donmountVFS_MOUNT. No loader, no kldload.

Sharp edge #1 — NO_MODULES static-link collisions The kernel is built NO_MODULES=yes and the image assembler rm -f /boot/kernel/*.ko — so HFS+ must be compiled in (like ZFS/geom_uzip/unionfs already are). As a .ko, freebsd_hfs's Darwin compat shim resolved its symbols privately; compiled statically, its Darwin vocabulary (vnode_t, mount_t, proc_t, uio_t, kauth_cred_t…) shares one namespace with the Mach/Apple compat layer already in this kernel (sys/apple/, compat/mach). Duplicate typedefs → redefinition errors, or worse, silently divergent struct layouts. The single most likely first build-break. Fix (modeled on the repo's COMPAT_MACH_C pattern): a scoped HFS_C compile line, and — critically — make HFS consume the repo's existing Apple typedef headers rather than its own, so there's exactly one vnode_t.
Sharp edge #2 — the FreeBSD 14 → 15 rebase freebsd_hfs targets 14; NextBSD is 15.1. The expensive VFS churn (the namei/componentname/thread cleanup, NDFREENDFREE_PNBUF) landed in 14, not 15 — so if the PoC genuinely crossed that, 14→15 is days of -Werror fixups (renamed struct fields, new-ish VOPs defaulting to vop_eopnotsupp, a vget/insmntque signature). UNVERIFIED: if it was actually written against 13.x with private shims, this balloons into a real port. Confirm the PoC's true baseline early.

The iterate loop is real

A push touching src-overlay/**/config/**/build.yml triggers a kernel rebuild + a qemu smoke-test that boots the image to login:. So "PR the driver, iterate until it works" genuinely works — each push rebuilds with HFS compiled in and boots it.

CI blind spot The smoke-test proves only that the kernel still boots — it never mounts or exercises HFS. Green CI on a kernel whose HFS driver corrupts every write is entirely possible. A mount + fsx/fsstress CI job must be added before "it works" means anything for a read-write filesystem.

5Utilities & mount — nextbsd-userland

The HFS tools are HFS-specific and FreeBSD base has none, so they belong in nextbsd-userland (the Apple system layer) as a pure addition — they supersede nothing. One bsd.prog.mk PROG per dir under src/hfs/, installed to /sbin (the mount/fsck helper path), not /usr/sbin.

ToolActionInstallsNote
newfs_hfsVendor/sbin/newfs_hfstouches raw device + hfs_format.h; no kernel ABI, no CF
fsck_hfsVendor/sbin/fsck_hfslargest tool; self-contained checker; the recovery path (needed before rw is safe)
mount_hfsVendor or ~150-line rewrite/sbin/mount_hfsthe one fork — see below
hfs.utilDefer(.fs bundle)DiskArbitration auto-mount hook; CF-dependent; add when diskarbitrationd reaches iter 5
"Swap FreeBSD mount for Apple mount" — actually just add a helper Nothing is swapped in mount(8) itself — keep FreeBSD's mount(8) verbatim. It already resolves -t hfs by exec'ing /sbin/mount_hfs. So you just add Apple's mount_hfs as that per-fstype helper (plus newfs_hfs/fsck_hfs), and mount -t hfs, fsck -t hfs, and fstab all work with zero changes to the mount framework.

The one branch: Apple's mount_hfs builds a struct hfs_mount_args and calls mount(2). It vendors verbatim if the freebsd_hfs kernel kept Apple's hfs_mount_args ABI; if the driver went FreeBSD-native nmount(2) instead, mount_hfs.c is a ~150-line NextBSD-original rewrite. This is the key kernel↔userland handshake to confirm. Top shim risk: <sys/attr.h>/getattrlist (used by fsck/newfs) isn't in the tree yet — one write-new shim. Format headers should be sourced from the kernel driver's exported headers (single source of truth).


6Staged roadmap — read-only first, root last

STAGE A

Compiles & boots

Land the overlay + options HFS; the boot smoke-test proves the kernel still boots with HFS linked in. Shakes out the static-link symbol collisions (#1) and the 14→15 -Werror fixups (#2). No mounting yet.

STAGE B

Read-only mount

Gate MNT_RDONLY; mount a macOS-made HFS+ image in a VM; verify catalog/extent traversal, ls -R, checksummed reads. Zero corruption surface. This is where case-folding (HFS+ case-insensitive vs HFSX) first bites — get it right here.

STAGE C

Read-write, non-root, data/shares only

Enable rw + journaling for data volumes / shares / removable media — never root. Highest corruption risk. Harness: fsx (data-integrity), fsstress (parallel metadata), mount/umount/fsck cycles, power-fail journal-consistency. Add the mount+fsx CI job here.

STAGE D

Feature completion

Port the remaining Apple code: resource forks, ACLs (→ FreeBSD VOP_[GS]ETACL), xattrs (→ VOP_[GS]ETEXTATTR), mmap. Each is an independent VOP-surface addition, testable in isolation. This is the layer that makes HFS+ worth it for AFP serving.

STAGE E

(Far future, optional) HFS+ as root

Only after C+D are corruption-clean. Requires changing the baked ROOTDEVNAME/vfs.root.mountfrom and — critically — surviving vfs.pivot's root-swap (mountcheckdirs() refcount balancing, union-layer membership). Keep HFS+ off the root path until then; UFS stays root, HFS+ serves data.


7Risks & open items

RiskAssessment
RW corruption (highest)A PoC-grade rw+journaling driver on real data is the dominant risk; journal-replay + crash-consistency is where HFS+ ports historically corrupt. Mitigate with read-only-first, rw-non-root-only, and mandatory fsx/fsstress+crash-injection in CI.
14→15 is a real port, not a rebaseUNVERIFIED — hinges on whether freebsd_hfs truly compiled against FreeBSD-14 VFS (post-namei cleanup). If 13.x-based, the estimate balloons. Confirm first.
Static-link symbol conflictsDarwin typedefs in the HFS shim vs the Mach/Apple compat layer already in this NO_MODULES kernel — most likely first build-break. Fix: scoped HFS_C flags + reuse the repo's Apple typedef headers.
mount_hfs ABIVendor-verbatim vs ~150-line rewrite depends on whether the kernel kept Apple's hfs_mount_args or went nmount(). Confirm at the kernel↔userland boundary.
vfs.pivot if HFS+ ever rootsStage E only; HFS+ would have to survive the union/root-swap refcount balancing. Kept off the root path until corruption-clean.
CI blind spotSmoke-test gates on login: only; add a mount+fsx job or "green" is meaningless for a filesystem.
License hygienefreebsd_hfs declares no repo license — add explicit APSL-2.0 before building on it.
Why this path is attractive despite the work It's a port of complete, open, battle-tested Apple code (not new design), it lands via an established precedent (vfs.pivot + COMPAT_MACH patterns), the utilities are a clean additive /sbin drop, the CI already builds-and-boots on every push, and the whole thing is de-riskable (read-only → rw-data → features; UFS stays root). And it's the authentic backing for a native AFP server — exactly the old-Mac stack this project exists to revive.


8Strategic direction — replacing UFS, then the FreeBSD userland

The longer-range vision this research serves: make HFS+ the filesystem, not just an add-on — and let it pull the userland toward Apple's

The staged roadmap above (§6) deliberately keeps UFS as root, HFS+ for data — the safe de-risking. The longer-range goal is more ambitious: HFS+ replaces UFS entirely, its Apple tools land in nextbsd-userland, the UFS equivalents are dropped from nextbsd-freebsd-compat, and — once the filesystem has moved — the FreeBSD POSIX userland is progressively swapped for Apple's system_cmds/file_cmds. This is the "become an authentic old Mac" direction, and it has a real technical logic, not just an aesthetic one.

Why the filesystem pulls the userland toward Apple On HFS+, files carry Mac metadata — resource forks, xattrs, Mac ACLs. FreeBSD's cp/ls/tar don't understand any of it — they silently drop forks and mangle metadata. Apple's file_cmds are the tools that handle it correctly. So on UFS, swapping FreeBSD tools for Apple's buys nothing (which is exactly why NextBSD's current design uses FreeBSD's); on HFS+, Apple's tools finally have a reason to exist. HFS+-first is therefore the correct order — the FS choice justifies the userland swap.

The conservative sequence (never lose bootability)

1. HFS+ as a DATA filesystem  ─────────  de-risk the driver (read-only → rw non-root)   [§6 A–C]
2. HFS+ rw corruption-clean  ──────────  fsx/fsstress/crash-injection all green         [§6 C]
3. Boot chain: small boot partition + HFS+ root  ──  rework livecd/vfs.pivot for HFS+ root  ← GATE
4. HFS+ boots reliably as root  ───────  NOW retire UFS + UFS tools from compat
5. Port Apple's HFS-metadata-aware file tools  ──  cp/ls/tar that handle forks (real reason)
6. (Optional) broader system_cmds swap  ──  purity, tool-by-tool, where it buys something
Gate — the boot chain (unscoped hard problem) FreeBSD's loader reads UFS/ZFS/FAT, not HFS+ — so you can't simply "boot HFS+." The tractable fix is the standard separate-/boot pattern: a small UFS or FAT/EFI boot partition holds loader + kernel; the loader loads the kernel from there; the kernel (HFS+ compiled in) mounts the HFS+ root — no HFS+-in-loader needed. What that does not avoid is reworking the livecd + vfs.pivot flow (today deeply UFS/unionfs-based) for an HFS+ root. This is the gate that decides whether HFS+-as-root is real, and it needs its own scoping.
Ordering rule — never drop UFS early The moment you remove UFS + its tools from compat, you've bet the ability to boot and build on the HFS+ driver being solid. So UFS stays until step 4 (HFS+ proven as a bootable root). A driver bug before then must never equal an unbootable, unbuildable system. (Nice consequence: the earlier compat audit flagged UFS tools as "impractical to port from Apple" because Apple has no UFS code — this plan resolves that: going HFS+ means using Apple's HFS tools and dropping UFS outright.)

Targeted vs. wholesale userland swap

Step 5 — the HFS+-metadata-aware tools (cp/ls/tar/archive) — is the part with a genuine capability reason and should be the real target. Step 6 — replacing the rest of the generic POSIX userland with Apple's — is mostly behavior-parity, not capability (Apple's build is functionally equivalent to FreeBSD's for generic tools), so it's a lot of shim work for authenticity alone. Do it gradually, tool-by-tool where it buys something, rather than as one big-bang replacement — unless authenticity itself is the goal, in which case it's a deliberate (large) cost.

Bottom line: the direction is coherent and on-brand — HFS+ root + Apple userland is literally "become an old Mac," and the FS→userland pull is a real justification. The cost is a multi-year program; the boot chain (step 3) is the gating unknown; and the governing rule is that UFS never leaves until HFS+ boots. The data-filesystem work (§6, steps 1–2) proves the driver before any of the riskier swaps begin.