-
Notifications
You must be signed in to change notification settings - Fork 0
HFS+ Filesystem
JNode's implementation of the HFS+ (Hierarchical File System Plus) filesystem used by classic Mac OS X.
HFS+ is a journaling filesystem originally developed by Apple for Mac OS 8.1 and later. JNode provides a read-only implementation supporting both standard HFS+ (magic 0x482b) and HFSX (magic 0x4858, with case-sensitive filenames).
| Class | Purpose |
|---|---|
HfsPlusFileSystem |
Main filesystem implementation extending AbstractFileSystem
|
HfsPlusFileSystemType |
Factory implementing BlockDeviceFileSystemType, detects HFS+/HFSX volumes |
SuperBlock |
Volume header at sector 2 (1024 bytes), contains magic, version, block size, allocation file descriptors |
Catalog |
B-Tree catalog file storing all file and directory records |
HfsPlusEntry |
Entry implementation bridging VFS to HFS+ catalog records |
HfsPlusDirectory |
Directory traversal via catalog B-Tree lookups |
HfsPlusFile |
File access with fork data (data fork, resource fork) |
Extent |
Extent overflow B-Tree for additional block allocations beyond the 8 extent limit |
Attributes |
Extended attributes B-Tree for security.xattr and compression attributes |
The volume header is located at byte offset 1024 (after the 512-byte boot sector plus 512 bytes padding). It contains:
-
Magic number:
0x482b(HFS+) or0x4858(HFSX) - Version: 4 (HFS+) or 5 (HFSX)
- Block size: typically 4096 bytes
-
Fork descriptors: pointers to the four special B-Tree files
- Allocation file (bitmap) — CNID 0x01
- Extents overflow file — CNID 0x02
- Catalog file — CNID 0x05
- Attributes file — CNID 0x0A
- Journal info block: pointer to journal if journaled
- Free block count: available allocation blocks
- File/folder counts: total entries
Volume header detection in HfsPlusFileSystemType.supports() reads sector 2 and checks the magic at offset 0 (big-endian).
HfsPlusFileSystemType.create(device, readOnly)
→ HfsPlusFileSystem(device, readOnly, this)
→ read()
→ new SuperBlock(this, false) // Load volume header
→ check HFSPLUS_VOL_JOURNALED_BIT // Force read-only for journaled
→ new Extent(this) // Extent overflow B-Tree
→ new Catalog(this) // Catalog B-Tree
→ new Attributes(this) // Extended attributes B-Tree
→ getRootEntry() // Returns root (CNID 2)
The catalog is a B-Tree storing all files and directories. Each key is a CatalogKey (parent CNID + Unicode name), and each leaf record contains either:
-
CatalogFolder— directory record with dates, permissions, folder ID -
CatalogFile— file record with fork data, dates, Finder info -
CatalogThread— reverse pointer from CNID to parent/name for efficient lookups by ID
The root entry (CNID 2, HFSPLUS_ROOT_CNID) is looked up via catalog.getRecord(CatalogNodeId.HFSPLUS_POR_CNID) where POR_CNID is the parent-of-root (CNID 1).
HfsPlusDirectory.getEntry(name) searches the catalog B-Tree:
- Start at root node (from
BTHeaderRecord.getRootNode()) - Navigate index nodes until reaching a leaf node
- Binary search leaf for key
(parentCNID, name) - Return
HfsPlusEntrywrapping the foundLeafRecord
HfsPlusFile reads file data via:
-
Direct extents (up to 8): stored in
CatalogFile.bsdInfo.forkData -
Overflow extents: looked up in the Extent B-Tree by
(fileID, forkType, startBlock)
Both use HfsPlusForkData to describe the allocation: start block, block count, clump size.
HFS+ stores hard-linked files and directories in hidden system directories:
-
\0\0\0\0HFS+ Private Data— hard-linked file data (CNID 8) -
.HFS+ Private Directory Data\r— hard-linked directory records (CNID 9)
getPrivateDataDirectory() and getPrivateDirectoryDataDirectory() lazily resolve these on access.
-
Read-only enforcement: Journaled volumes are mounted read-only regardless of the
readOnlyflag -
Write support stubbed:
create()andwriteAllocationFile()contain FIXME comments - No write-back caching: No dirty-bit tracking or consistency recovery
- Hard link handling: Requires scanning the private data directory to resolve link targets
- Unicode normalization: HFSX supports case-sensitive names; JNode does not normalize Unicode
- No journal replay: Mounting a dirty journaled volume as read-only skips journal recovery