FAT12 on 1.44 MB Floppy Images: Layout, Metadata, and a Linux Walkthrough
FAT12 on 1.44 MB media organizes space using clusters (allocation units), which are made of one or more sectors; sectors are the smallest addressable blocks. A standard 3.5" floppy uses 80 cylinders × 2 heads × 18 sectors per track × 512 bytes per sector = 2880 sectors (1,474,560 bytes).
Logical sectors (LBA) are numbered from 0 upward. On typical controllers the medium is read cylinder-by-cylinder; CHS-to-LBA mapping for 1.44 MB is LBA = (cylinder × heads + head) × sectors_per_track + (sector − 1), with heads=2 and sectors_per_track=18.
FAT12 volume layout (1.44 MB, defaults)
- Sector 0: Boot sector (BPB + bootstrap)
- Sectors 1–9: FAT #0 (9 sectors)
- Sectors 10–18: FAT #1 (mirrored copy)
- Sectors 19–32: Root directory (14 sectors; 224 entries × 32 bytes)
- Sectors 33–2879: Data region (clusters start here)
With SecPerClus=1, the first data sector is 33. The LBA of cluster N is first_data_lba + (N − 2) × SecPerClus; for this format: LBA(cluster N) = 33 + (N − 2).
Boot sector and BPB (FAT12) The 512-byte boot sector carries the BIOS Parameter Block (BPB), optional boot code, and the signature 0x55AA at bytes 510–511.
Typical field layout for a 1.44 MB FAT12 volume:
| Field | Offset | Size | Meaning | Typical |
|---|---|---|---|---|
| BS_jmpBoot | 0 | 3 | x86 jump to boot code | EB 3C 90, etc. |
| BS_OEMName | 3 | 8 | OEM ID (8 chars) | "mkfs.fat" |
| BPB_BytesPerSec | 11 | 2 | Bytes per sector | 0x0200 (512) |
| BPB_SecPerClus | 13 | 1 | Sectors per cluster | 0x01 |
| BPB_RsvdSecCnt | 14 | 2 | Reserved sectors | 0x0001 |
| BPB_NumFATs | 16 | 1 | FAT copies | 0x02 |
| BPB_RootEntCnt | 17 | 2 | Root entries | 0x00E0 (224) |
| BPB_TotSec16 | 19 | 2 | Total sectors (if < 65536) | 0x0B40 (2880) |
| BPB_Media | 21 | 1 | Media descriptor | 0xF0 |
| BPB_FATSz16 | 22 | 2 | Sectors per FAT | 0x0009 |
| BPB_SecPerTrk | 24 | 2 | Sectors/track | 0x0012 (18) |
| BPB_NumHeads | 26 | 2 | Number of heads | 0x0002 |
| BPB_HiddSec | 28 | 4 | Hidden sectors | 0x00000000 |
| BPB_TotSec32 | 32 | 4 | Total sectors if TotSec16==0 | 0x00000000 |
| BS_DrvNum | 36 | 1 | Int 13h drive number | 0x00 |
| BS_Reserved1 | 37 | 1 | Reserved | 0x00 |
| BS_BootSig | 38 | 1 | Extenedd signature | 0x29 |
| BS_VolID | 39 | 4 | Volume serial | varies |
| BS_VolLab | 43 | 11 | Volume label (11 chars) | "NO NAME " |
| BS_FileSysType | 54 | 8 | Filesystem tag (8 chars) | "FAT12 " |
| Boot code | 62 | 448 | Bootstrap/data | implementation-defined |
| Signature | 510 | 2 | 0x55AA | 0xAA55 |
FAT (File Allocation Table)
- Two identical copies (FAT1 and FAT2), each 9 sectors.
- Each FAT12 entry is 12 bits; entries 0 and 1 are reserved.
- Entry values:
- 0xFF0–0xFF6: reserved
- 0xFF7: bad cluster
- 0xFF8–0xFFF: end-of-chain (EOC)
- other: next cluster in the chain
- Cluster numbering begins at 2; cluster 2 maps to the first cluster in the data region.
- Packing: two 12-bit entries are stored in three bytes. For entries n (even) and n+1:
- byte0 = low 8 bits of entry n
- byte1 = high 4 bits of entry n (low nibble) | low 4 bits of entry n+1 (high nibble)
- byte2 = high 8 bits of entry n+1
Root directory entries (SFN) Each directory entry is 32 bytes. Root directory size is fixed by BPB_RootEntCnt (224 entries → 14 sectors).
| Offset | Size | Description |
|---|---|---|
| 0 | 8 | Filename (padded with spaces) |
| 8 | 3 | Extension |
| 11 | 1 | Attributes |
| 12 | 2 | Reserved (NT/Case) |
| 14 | 2 | Creation time |
| 16 | 2 | Creation date |
| 18 | 2 | Last access date |
| 20 | 2 | High word of first cluster (FAT16/32; ignored in FAT12) |
| 22 | 2 | Last write time |
| 24 | 2 | Last write date |
| 26 | 2 | First cluster (FAT12) |
| 28 | 4 | File size in bytes |
Attributes include: 0x10 (directory), 0x08 (volume label), 0x20 (archive), 0x02 (hidden), 0x01 (read-only), and the 0x0F LFN marker for long filename entries.
Long File Name (LFN) entries LFNs are stored as one or more special entries (attribute 0x0F) placed immediately before the short-name entry. Each LFN entry is 32 bytes with UTF-16 name fragments.
| Byte range | Description |
|---|---|
| 0 | Ordinal (1..n, high bit set in the last LFN entry) |
| 1–10 | Name chars (5 UTF‑16 code units) |
| 11 | Attribute = 0x0F |
| 12 | Type (0) |
| 13 | Checksum of SFN |
| 14–25 | Name chars (6 UTF‑16 code units) |
| 26–27 | Must be 0 |
| 28–31 | Name chars (2 UTF‑16 code units) |
Allocation differences
- Root directory items live in the dedicated root region; creating a file or a directory there consumes one 32-byte entry (plus LFN entries if present). The data for a file/directory consumes clusters from the data region.
- Creating a subdirectory allocates at least one cluster for its own directory table (with "." and ".." entries) and one directory entry in its parent.
Data region and cluster chains Files occupy one or more clusters, linked by the FAT. The first cluster is in the directory entry; follow FAT entries untill an EOC value.
Linux walkthrough: create, inspect, and manipulate a FAT12 image
Create a 1.44 MB image Option A (truncate):
- truncate -s 1440K floppy.img
Option B (dd):
- dd if=/dev/zero of=floppy.img bs=1K count=1440 status=none
Format as FAT12
- mkfs.fat -F 12 -S 512 -s 1 -n "NO NAME" floppy.img
Peek at the boot sector and signature
- xxd -g 1 -l 64 floppy.img
- tail -c 2 floppy.img | xxd -g 1
Extract the FAT and root directory
- dd if=floppy.img bs=512 skip=1 count=9 status=none | xxd -g 1 | head
- dd if=floppy.img bs=512 skip=19 count=14 status=none | xxd -g 1 | head
Mount, write a file, unmount
- sudo mkdir -p /mnt/f12
- sudo mount -o loop,uid=$(id -u),gid=$(id -g) floppy.img /mnt/f12
- printf "hello\n" > a.txt && cp a.txt /mnt/f12/
- sync && sudo umount /mnt/f12
Interpret the structures after copying a.txt
- Root directory starts at LBA 19 → offset 19×512 = 0x2600. One entry for a short name is 32 bytes; if an LFN was produced by the OS, one or more 0x0F entries precede the base entry.
- The first cluster field (offset 26 in the base entry) might be 0x0003 on a freshly formatted volume. With SecPerClus=1, LBA = 33 + (3 − 2) = 34 → offset 0x4400 for file data.
- FAT1 begins at LBA 1. The first three bytes are typically F0 FF FF (FAT[0]=0xFF0, FAT[1]=0xFFF). If the file uses cluster 3 and ends there, FAT[3]=0xFFF. Entries 2 (free) and 3 (EOC) are packed as 00 F0 FF.
Sample bytes from FAT showing entries 0–5 after creating one single-cluster file at cluster 3:
- dd if=floppy.img bs=1 skip=$((5121)) count=$((34)) status=none | xxd -g 1 -l 24
You should see a sequence beginning like:
- f0 ff ff | 00 f0 ff | 00 00 00 ...
- f0 ff ff → FAT[0]=FF0, FAT[1]=FFF
- 00 f0 ff → FAT[2]=000, FAT[3]=FFF
Data region start and a.txt content
- First data sector: 33 → offset 0x4200 for LBA 33, 0x4400 for LBA 34 (cluster 3). Inspect with:
- dd if=floppy.img bs=512 skip=34 count=1 status=none | xxd -g 1
Create a file larger than one sector and a subdirectory
- sudo mount -o loop,uid=$(id -u),gid=$(id -g) floppy.img /mnt/f12
- head -c 522 /dev/zero > big.bin && cp big.bin /mnt/f12/
- mkdir /mnt/f12/dir
- sync && sudo umount /mnt/f12
Expected FAT changes (illustrative)
- Suppose big.bin gets clusters 4 → 5 → EOC. Entries 4 and 5 pack as 05 F0 FF.
- A newly created subdirectory "dir" may be allocated cluster 6 with EOC; entries 6 and 7 pack into FF 0F 00 (entry 6 = FFF, entry 7 = 000).
You can verify with:
- dd if=floppy.img bs=512 skip=1 count=1 status=none | xxd -g 1 | head
- Look for sequences f0 ff ff (entries 0–1), 00 f0 ff (2–3), 05 f0 ff (4–5), ff 0f 00 (6–7).
Inspect the subdirectory’s data
- Cluster 6 LBA = 33 + (6 − 2) = 37 → offset 0x4A00. Dump it:
- dd if=floppy.img bs=512 skip=37 count=1 status=none | xxd -g 1
- The first two entries are "." and "..". Additional entries describe files in that subdirectory.
Create a long filename and a file in the subdirectory
- sudo mount -o loop,uid=$(id -u),gid=$(id -g) floppy.img /mnt/f12
- printf "content\n" > /mnt/f12/abcdefghijklmn.txt
- printf "aaaaaaaa\n" > /mnt/f12/dir/c.txt
- sync && sudo umount /mnt/f12
Root directory view for the long name
-
Dump the root region again and look for a sequence of LFN entries (attribute 0x0F) immediately before the base 8.3 entry. For "abcdefghijklmn.txt" the LFN spans multiple entries; the SFN may appear as ABCDEF~1.TXT.
-
dd if=floppy.img bs=512 skip=19 count=2 status=none | xxd -g 1 | sed -n '1,80p'
File in subdirectory
-
Locate the directory "dir" entry (in the root) to read its first cluster, then dump that cluster to find c.txt’s directory entry. Its first cluster value maps to a single sector if FAT shows EOC right away.
-
Example: if c.txt uses cluster 9 and FAT[9]=FFF, the file is one cluster long. Its content lives at LBA 33 + (9 − 2) = 40.
-
dd if=floppy.img bs=512 skip=40 count=1 status=none | xxd -g 1
Quick reference formulas
- root_dir_sectors = ceil((BPB_RootEntCnt × 32) / BPB_BytesPerSec)
- first_data_lba = BPB_RsvdSecCnt + (BPB_NumFATs × BPB_FATSz16) + root_dir_sectors
- lba(cluster) = first_data_lba + (cluster − 2) × BPB_SecPerClus
- max clusters (FAT12) ≈ floor((FATSectors × 512 × 8) / 12)
On a 1.44 MB image with the defaults shown: root_dir_sectors=14, first_data_lba=1 + (2×9) + 14 = 33, and LBA(cluster N)=33 + (N − 2).