Skip to content

FAT Filesystem

LSantha edited this page Oct 20, 2026 · 3 revisions

FAT Filesystem

JNode's FAT12/FAT16/FAT32 filesystem implementation with Long File Name (LFN) support.

Overview

The FAT (File Allocation Table) filesystem in JNode provides read-write support for FAT12, FAT16, and FAT32 variants. It integrates with JNode's VFS layer through the FatFileSystemType factory and uses the block device layer for sector-level I/O. The implementation includes support for Long File Names (VFAT) to overcome the 8.3 filename limitation.

Key Components

fat Package (Legacy Implementation)

Class / File Purpose
fs/src/fs/org/jnode/fs/fat/ Original FAT12/FAT16/FAT32 implementation — legacy, less actively maintained

jfat Package (Active Implementation)

Class / File Purpose
FatFileSystemType.java Factory implementing FileSystemType, probes block devices to determine FAT type
FatFileSystem.java Main filesystem, extends AbstractFileSystem, manages FAT and root entries
BootSector.java BPB parsing (offsets 11-64), FAT type detection via RootEntCnt==0 && FATSz16==0, computes CountOfClusters, DataSec, FirstDataSector
Fat.java Abstract FAT table, cluster chain operations, size() returns CountOfClusters + 2
Fat32.java FAT32 variant — 4 bytes per entry, EOF mask 0x0FFFFFFF
Fat16.java FAT16 variant — 2 bytes per entry, EOF threshold 0xFFF8
Fat12.java FAT12 variant — 1.5 bytes per entry, EOF threshold 0xFF8
FatChain.java Cluster chain traversal, allocateAndClear(), read(), write() methods
FatFile.java File I/O via cluster chain reads/writes
FatDirectory.java Directory iteration via FatEntriesFactory
FatRootDirectory.java FAT32 root dir stored in cluster (not fixed-size like FAT12/16)
FatEntriesFactory.java Scans directory entries, handles chain errors gracefully
FatShortDirEntry.java, FatLongDirEntry.java Short 8.3 and Long File Name directory entries

Architecture

Filesystem Type Registration

The FatFileSystemType class implements the FileSystemType interface, allowing the VFS to probe block devices and instantiate FatFileSystem instances. This follows the plugin extension point pattern used throughout JNode.

Boot Sector Parsing

The BootSector class parses the BIOS Parameter Block (BPB) from the first sector:

  • getBytesPerSector(), getSectorsPerCluster() — cluster size calculation
  • getNrFats() — number of FAT copies
  • getSectorsPerFat() — FAT size
  • getNrRootDirEntries() — root directory size (FAT12/FAT16 only)
  • getMediumDescriptor() — determines FAT12 vs FAT16

FAT Table Management

The Fat class handles cluster chain operations:

  • Read/Write: Binary encoding for each FAT variant (1.5 bytes/entry for FAT12, 2 bytes for FAT16, 4 bytes for FAT32)
  • Cluster allocation: allocNew(), allocAppend() for file growth
  • Chain traversal: getChain(), getNextCluster() for file I/O
  • EOF detection: Uses variant-specific EOF markers from FatType

Directory Structure

FatDirectory extends AbstractDirectory and handles:

  • Root directory vs subdirectory (root has fixed size, subdirectories are cluster-based)
  • Entry iteration via FSEntry interface
  • Label (volume name) management in root directory
  • LFN sequence handling via FatLfnDirectory

LFN Support

Long File Names are stored in special directory entries (FatLfnDirEntry) preceding the 8.3 alias entry:

  • Unicode name storage (up to 255 characters)
  • Checksum-based association with the short name
  • Unicode conversion via LfnEntry utilities

How It Works

Mounting a FAT Volume

FatFileSystem constructor:
1. Read boot sector (512 bytes)
2. Determine FAT type from medium descriptor (0xf8 = FAT16)
3. Create Fat instances for each FAT copy
4. Read root directory (fixed-size for FAT12/16, cluster-based for FAT32)
5. Validate FAT consistency across copies

File Access

FatFile.getData():
1. Get starting cluster from directory entry
2. Fat.getChain(startCluster) → array of cluster numbers
3. Map clusters to sector offsets
4. Read data from block device

Writing Changes

FatFileSystem.flush():
1. Write dirty boot sector
2. Flush all open FatFile instances
3. Write dirty FAT copies
4. Flush dirty root/subdirectories

JFAT Durability and Allocator Fixes (2026-10-14)

Commit 18fb3f1bf (#666) fixed four defects that showed up as data loss or allocation failure:

  • flush() lost data. FatEntry.flush() wrote the directory entry but not the FAT table, so a file longer than one cluster could read back truncated after a remount. It now calls getFatFileSystem().getFat().flush() and getApi().flush(), flushing both the FAT and the block device.
  • A free-hint wraparound allocated the wrong cluster. FatChain.allocateAndClear scans from fat.getLastFree() to the end, then wraps to firstCluster(). The wrap loop overwrote l on every free entry (instead of keeping the last one from the suffix scan), and it could fall through with l still 0 when nothing was free. The loop now tracks suffixFound/l correctly, bounds every backward scan at firstCluster(), and throws FileSystemFullException when the allocation is short — instead of returning a bogus chain.
  • A circular chain looped forever. The FatChain iterator now throws IOException("circular chain at: …") once its index reaches fat.size(), rather than following the cycle indefinitely.
  • Mounting raced device start. FileSystemMounter.deviceStarted queued mounting through WorkUtils.add(...), so a filesystem could be used before the mount finished. It now calls asyncDeviceStarted(device) synchronously from the listener.

FatWriteTest gained two regressions: testFileFlushPersistsExtendedFile (3-cluster file, close, reopen read-only, compare bytes) and testAllocationWrapsWhenNoFreeClustersAfterHint (exhaust the suffix, allocate one cluster, read it back).

Gotchas & Non-Obvious Behavior

  • Root directory: FAT12/FAT16 root has fixed size from BPB; FAT32 root is a cluster chain like any directory
  • Cluster size: Derived from bytesPerSector * sectorsPerCluster, used for all allocation
  • Dirty tracking: Files, FAT, and directories track changes independently; flush must handle all three. FatEntry.flush() must flush the FAT and the block device, or a multi-cluster file reads back short after remount.
  • The free-cluster hint is only a hint: allocation scans to the end and then wraps, but every scan must be bounded at firstCluster() and the result checked; an unbounded wrap returns cluster 0 or a short chain.
  • Circular chains are detected by an index bound, not by a visited set: the iterator throws once index >= fat.size().
  • FAT consistency: On mount, JNode logs warnings if FAT copies differ (first FAT is used)
  • LFN ordering: LFN entries must immediately precede their 8.3 alias; deletion disrupts the chain
  • FAT32 detection (jfat): Must use RootEntCnt==0 && FATSz16==0 BEFORE computing type — Microsoft spec. Some mkfs.fat versions set TotSec16 with actual value while TotSec32=0, breaking naive detection
  • FAT32 CountOfClusters (jfat): Must be computed in FAT32 branch of compute() — DataSec = TotSec - FirstDataSector, not including RootDirSectors which is 0 for FAT32

Related Pages

Clone this wiki locally