NextBSD · Kernel patch · Plan

Case-insensitive UFS — a nextbsd-kernel patch

The pragmatic alternative to porting a whole HFS+ filesystem just to get Mac-friendly case behavior: teach FreeBSD's UFS/FFS to fold case at name lookup. A tightly-bounded patch — three code changes and a superblock flag — that gives Macs the case-insensitivity Apple's own UFS never had.

Scoped 2026-07-16 from a source-grounded agent reading the baked freebsd-src (releng/15.1) + nextbsd-kernel. Half of the pragmatic "UFS + AFP" path (companion: native AFP server). Planning only — no code written.

Verdict

A genuinely small, ~4–6 week kernel change for the ASCII-only version — versus years for the HFS+ port that HFS+ was partly being adopted to get case-insensitivity from. The whole fold is essentially three lines and a flag.

1The patch surface — three changes + a flag

All in sys/ufs/; line numbers from the baked releng/15.1 tree

SiteChange
ufs/ufs_lookup.cufs_lookup_ino() ~l.422The single name-match bcmp(cn_nameptr, ep->d_name, namlen)THE comparison. Fold it. Because CREATE/RENAME/DELETE all run ufs_lookup_ino first, fixing this one point also makes create/rename collision detection case-insensitive (creating FOO when foo exists now correctly returns EEXIST).
ufs/ufs_dirhash.cufsdirhash_hash() ~l.1033The keystone. fnv_32_buf(name,...) must hash the case-folded bytes, so foo and FOO land in the same slot (or the fast path silently misses case-variants the linear path would hit).
ufs/ufs_dirhash.cufsdirhash_lookup() ~l.645The dirhash verify compare — fold it too, matching l.422.
ffs/fs.h struct fs.fs_flagsAdd FS_CASEFOLD (next free bit, alongside FS_ACLS/FS_NFS4ACLS/…); latch into ufsmount in ffs_mountfs() so the fold path is gated per-volume.
Case is preserved on disk Leave ufs_makedirentry()'s name write verbatim — the original case is stored; only comparison and hashing fold. Same as ext4/HFS+ ("case-insensitive, case-preserving"). The single ufs_fold() helper must live in a shared header so fsck_ffs can #include the identical logic — any drift between kernel and fsck is a guaranteed corruption source.

2Precedent — and why it's not Apple

SystemFoldNormalize?EnableBorrow
Linux ext4/f2fs "casefold" (2019)fs/unicode utf8_casefold over a trie, at compare+hashNFD + full case-foldsuperblock encoding stamp + per-dir +F flagThe whole model — closest analogue
FreeBSD msdosfs (already CI, in-tree)win2unixfn + iconv codepage foldcodepage, not Unicodemount flags (MSDOSFSMNT_*)Proves an in-kernel CI FS ships in FreeBSD today; mount-flag gate is idiomatic
HFS+ (Apple)FastUnicodeCompare + gLowerCaseTable (compact, no ICU)NFD on createvolume-wideCompact fold table — but APSL (same provenance burden as the HFS+ port)
Two signals worth heeding Apple's UFS was case-sensitive — Apple never made a case-insensitive UFS; case-insensitivity was HFS+'s domain, and Apple's UFS being case-sensitive was itself a compatibility headache. And ravynos — a FreeBSD fork whose entire purpose is macOS compatibility — did not add UFS case-insensitivity (verified: no casefold/nfd/unicode in its sys/ufs/). A project that would most want this chose not to build it. So: novel, tractable, but the effort/risk is real enough that a Mac-compat project passed. The framing that survives: this is "Apple's UFS + the case-insensitivity Apple never bothered to add."

3Design decisions

ChoiceRecommendation
ASCII-only fold vs full UnicodeASCII-only first. ~5-line tolower, no in-kernel table, handles English/code/app-bundle names (the vast majority). Non-ASCII names simply stay case-sensitive — never corrupts. Build the machinery so a Unicode table drops into the one ufs_fold() later. Full Unicode = a ~324 KB table in a NO_MODULES kernel + the provenance problem → defer.
Enable granularitySuperblock flag, volume-wide (set by newfs/tunefs, read at mount). Matches HFS+/msdosfs, touches no inode format. Skip a mount option as the primary switch (a CI volume mounted case-sensitive = collision hazard); allow a mount option only to override off for recovery.
NormalizationCasefold only, no NFD, in v1. NFD-on-write changes on-disk bytes (breaks POSIX "bag of bytes" + needs the table). Accept the gap: names differing only by NFC/NFD stay distinct (same as APFS default). Add NFD later if real interop failures appear.

4Userland: fsck_ffs / newfs / tunefs

These are FreeBSD-base tools (fbsdglue), so changes are source patches in nextbsd-freebsd-compatbut that repo is scaffold-only today (the fbsdglue source hasn't migrated yet), so these are blocked on that migration.


5Landing in nextbsd-kernel

Edits to existing sys/ufs/ files → a single patches/ entry (not src-overlay/). The overlay repo applies patches/series over the baked /usr/src.

patches/0008-ufs-caseinsensitive.patch   # edits ufs_lookup.c, ufs_dirhash.c, fs.h,
                                          # ufsmount.h, ffs_vfsops.c + a new ufs_casefold.h
patches/series          # append the one line (verbatim, no comments)
config/NEXTBSD          # add:  options UFS_CASEFOLD   (#ifdef-gated code)
      # register the option token in sys/conf/options (opt_ufs.h already exists,
      # used by UFS_DIRHASH/UFS_EXTATTR) — add within the patch, no build.yml change

Iterate loop: a push touching patches/**/config/** rebuilds the kernel (NO_MODULES=yes) and, on a PR, boots the image under qemu — but the boot smoke-test only proves boot (root is a normal case-sensitive UFS), so you must add a functional test: newfs a scratch image with the flag, mount, touch FOO; stat foo, verify the collision.


6Effort & risks

PieceEffort
ASCII-only fold, kernel only (working → hardened + qemu test)~1 → 2–3 weeks
newfs/tunefs flag~2–3 days (blocked on fbsdglue migration)
fsck_ffs collision detection~1–2 weeks
Shippable ASCII-only CI-UFS w/ tools + fsck~4–6 weeks
Full Unicode NFD+CF (table import + provenance + fsck sharing)+1–3 months
Risk
Stock-FreeBSD read = silent data hazard (highest)A FS_CASEFOLD volume mounted on vanilla FreeBSD (or with the option not compiled) mounts case-sensitive and ignores the flag → foo/FOO become two files → a later CI mount sees a fold-collision. Stock fsck_ffs won't flag it. Accept "NextBSD-only volumes" (or use a feature bit stock fsck rejects).
dirhash correctness (high)If the fold in the hash and the fold in the compares ever disagree → intermittent "file exists but can't be opened." Single shared ufs_fold() mandatory; existing dirhashes must be invalidated when fold logic changes.
fsck/kernel fold divergence (high)fsck must use the identical fold routine or it mis-classifies collisions.
rename case-change (medium)rename(foo→FOO) on a CI volume is an in-place case rewrite (allowed, must update on-disk name); rename(foo→BAR) is distinct. Stock ufs_rename doesn't distinguish — needs explicit handling.
Unicode table provenance (medium, only if full-Unicode)The one ready fold table is HFS+'s gLowerCaseTable (APSL) — importing it re-imports the provenance burden HFS+ was avoided to dodge. Generating fresh from the UCD sidesteps it but is extra tooling.
Bottom line ASCII-only, superblock-flag, casefold-no-normalize, #ifdef UFS_CASEFOLD + a shared ufs_fold() is a genuinely pragmatic ~4–6 week path to Mac-friendly case behavior — provided you accept NextBSD-only volumes and defer Unicode/NFD. Paired with the AFP server, it delivers "serve Macs case-insensitively over AFP" at a fraction of the HFS+ port's cost. That even ravynos skipped it should temper expectations — the fsck-consistency and on-disk-compat tails are where the time goes.