This is a documentation about exFAT filesystem written based on the US. Pat. App. Pub. No. 2009/0164440 A1 and some researches in behavior of standard systems (real Windows). It is for a supplemental attached to FAT Filesystem. You need to understand the FAT filesystem prior to read this documentation. There is a possibility of some unintended or intended error in this documentation, so that you need to refer the primary sources of exFAT filesystem and make sure behavior of the real systems when write any exFAT driver or utility.
The exFAT filesystem (Microsoft Extended FAT Filesystem) was developped as a succession of industry standard FAT filesystem widely used for removable media and built-in storages in embedded systems. For this reason, it is designed based on the FAT filesystem to be easily implemented to existng systems. This documentation describes mainly the differences between these filesystems. exFAT filesystem has some advantages of FAT filesystem as follows.
Numbers with heading 0x are assumed to be hexadecimal numbers, and others are decimal numbers.
The prefix K, M, G and T of each unit is assumed to be 210, 220, 230 and 240 respectively.
The fragment of program codes contained in this document are written in C language, but it is not strict to the syntax.
32-bit value and 16-bit value are arbitrary mixed in the fragment of the program codes. The programmer needs be aware of the data loss due to the type conversion and how to avoid it. Also, every data type is assumed to be unsigned. Do not calculate in signed, or it may result unintended results.
The exFAT volume is composed of three areas. Each area is located on the volume in order of as follows: (exFAT Volume Map)
Volume configuration parameters are recoreded in the boot sector as FAT filesystem does. The exFAT boot sector is actually in 12 contiguous sectors block, and two set of boot sectors (main and backup) are set in the boot area where the top of the exFAT volume. Each boundaries, sizes and count of clusters are explicitly recorded in the boot sector and there is nothing unclear in volume recognization.
Field name | Offset | Size | Description |
---|---|---|---|
JumpBoot | 0 | 3 | Jump instruction to the BootCode (x86 instruction). Must be 0xEB, 0x76, 0x90. |
FileSystemName | 3 | 8 | Filesystem name. Must be "EXFAT ". |
MustBeZero | 11 | 53 | This field must be filled with zeros and spans where the BPB of FAT filesystem. This is to prevent misrecognizing as FAT volume. |
PartitionOffset | 64 | 8 | Offset of this exFAT volume origin from top of the hosting physical drive in unit of sector. The value zero indicates this field has no meaning and should be ignored. |
VolumeLength | 72 | 8 | Size of this exFAT volume includes three areas (boot, FAT and data) in unit of sector. Unlike the FAT volume, this value must be exactly same as the size of its container (the partition in MBR format or the pysical drive in SFD format). Volume size must be equal or larger than 1 MB. |
FatOffset | 80 | 4 | Offset of FAT area origin from top of the volume in unit of sector. |
FatLength | 84 | 4 | Size of an FAT in unit of sector. |
ClusterHeapOffset | 88 | 4 | Offset of cluster heap (data area) origin from top of the volume in unit of sector. |
ClusterCount | 92 | 4 | Number of clusters on the volume. 0xFFFFFFF5 maximum. |
FirstClusterOfRootDirectory | 96 | 4 | First cluster number of the root directory. |
VolumeSerialNumber | 100 | 4 | Volume serial number. This value is a random number typically generated from current time and date. |
FileSystemRevision | 104 | 2 | Filesystem revision. Upper byte indicates the major number and lower byte inidicates the minor numer, e.g. 0x020B indicates the revision 2.11. This document describes about exFAT revision 1.0. |
VolumeFlags | 106 | 2 | This field contains some flags to indicate status of this volume as described below: bit0 (ActiveFat): 0 indicates the first FAT and allocation bitmap are used, 1 indicates the second FAT and allocation bitmap are used (TexFAT option) bit1 (VolumeDirty): Set on mount, restored on unmount. If it is already set on mount, it means there is a possibility of some insanity in the volume. Cleard after a volume sanity check. bit2 (MediaFailure): Set on unrecoverable disk error, cleared after a surface scan. bit3-15: Reserved (0). |
BytesPerSectorShift | 108 | 1 | Sector size in unit of byte expressed in log2. Valid value for this field is 9 to 12 (512 to 4096 bytes). It should be same as read/write block size of the storage device that hosts this volume. |
SectorsPerClusterShift | 109 | 1 | Cluster size in unit of sector expressed in log2. Valid value for this field is 0 to 25-BytesPerSectorShift (one sector to 32 MB). |
NumberOfFats | 110 | 1 | Number of FATs and allocation bitmaps. 1 indicates one FAT and one allocation bitmap are exist. 2 indicates two FATs and two allocation bitmaps are exitst. Always 1 for removable media, 2 for TexFAT volume option. |
DriveSelect | 111 | 1 | Drive number used by disk BIOS (typically 0x80). This field is used in bootstrap program. Actually it depends on the OS. |
PercentInUse | 112 | 1 | Volume usage in unit of percent. 0xFF means this field is not used and invalid. |
Reserved | 113 | 7 | Reserved and should be filled with zero. |
BootCode | 120 | 390 | Boot program. This is a system dependent field and filled with zeros when not used. |
BootSignature | 510 | 2 | 0xAA55. Boot signature used to validate that this is a boot sector. |
512 | If the sector size is larger than 512 bytes, rest field in the sector is undefined. |
Field name | Offset | Size | Description |
---|---|---|---|
ExtendedBootCode | 0 | 2BytesPerSectorShift-4 | Extended boot code. This is system dependent field and filled with zero when not used. |
ExtededBootSignature | 2BytesPerSectorShift-4 | 4 | 0xAA550000. Boot signature used to validate that this is an extended boot sector. |
Sector 9 contains OEM parameter and sector 10 is reserved for future use. The content of these sectors is depends on the system and filled with zero when not used. Sector 11 is filled with 32-bit check sum values of from sector 0 to sector 10 inclusive. These 11 sectors are calculated in byte-by-byte as a simple byte array, however VolumeFlags and PercentInUse are excluded from the calculation.
/* Calculation of 32-bit check sum */
uint32_t sum32 (const void* p, uint32_t n)
{
uint32_t sum = 0;
const uint8_t *dp = (const uint8_t*)p;
do {
sum = ((sum & 1) ? 0x80000000 : 0) + (sum >> 1) + *dp++;
} while (--n);
return sum;
}
/* Calculation of 16-bit check sum */
uint16_t sum16 (const void* p, uint32_t n)
{
uint16_t sum = 0;
const uint8_t *dp = (const uint8_t*)p;
do {
sum = ((sum & 1) ? 0x8000 : 0) + (sum >> 1) + *dp++;
} while (--n);
return sum;
}
The exFAT FAT entry is 32-bit in size as FAT32 FAT entry except for all bits in each entries are used. exFAT has adopted allocation bitmap to manage the cluster allocation status, free or not. The first bit of the allocation bitmap (bit0 of the first byte) corresponds to the cluseter 2. The bit value 1 means the cluster is in-use and not free for new allocation. The bit value 0 means the cluster is free and the value of FAT entry has no meaning. This is a significant difference of exFAT filesystem from FAT filesystem which indicates free cluster by a zero in the FAT entry. Following table shows the status of a cluster and corresponding entries. In the special case, the value in the FAT entry can have no meaning even if the cluster is in-use. This is described in Directory Operations.
bitmap | FAT | Cluster status |
---|---|---|
0 | (Don't care) | Free |
1 | 0 - 1 | (Reserved) |
1 | 2 - ClusterCount+1 | In-use (value is link to next) |
1 | ClusterCount+2 - 0xFFFFFFF6 | (Reserved) |
1 | 0xFFFFFFF7 | Bad cluster |
1 | 0xFFFFFFF8 - 0xFFFFFFFE | (Reserved) |
1 | 0xFFFFFFFF | In-use (end of chain) |
The allocation bitmap is recored in the data area and its allocation information (start cluster and size) is recorded as a special directory entry (described in Directory Entry) on the root directory. The sizeof allocation bitmap becomes (ClusterCount + 7) / 8 bytes.
In the exFAT filesystem, file names are handled as LFN extension of FAT filesystem does. Case information is preserved on recording, and matched in case insensitive. exFAT volume has up-case table on the volume for up-case conversion while it is done with on-system conversion table at the FAT filesystem. Perhaps, this is for new character definitions in the future. However what occures on the compatibility between the changes and existing systems is not clear. The up-case table needs to support ASCII alhpabetic character at least, and non-ASCII characters are optional. According to a research into the exFAT volume formatted by Windows, it seems to be same as FAT filesystem on the Windows does, but it is not that kept unchanged in the future.
The up-case table seems to support only BMP where corresponds to U+0000 - U+FFFF. It is recorded on the volume in compressed form. The following function is to extract the compressed table and create a case conversion table.
void load_upcase ( uint16_t dst[], /* Output table for U+0000 - U+FFFF */ uint16_t src[], /* Compressed table to input */ uint16_t n_src /* Size of compressed table [items] */ ) { uint16_t c, si, di; /* Fill output table with default values */ c = 0; do dst[c] = c; while (++c); si = di = 0; do { c = src[si++]; /* Get an up-case character */ if (c == 0xFFFF && si < n_src) { di += src[si++]; /* When U+FFFF appeared, skip the codes indicated by next item */ } else { dst[di++] = c; /* Store the up-case character */ } } while (si < n_src); }
Up-case table is recorded in the data area and its allocation information (start cluster and size) is recorded in a special directory entry (this is described below) on the root directory.
Root directory is recorded in the data area. The first cluster number is stored in FirstClusterOfRootDirectory. The size of directory entry is 32 bytes. These are saeme as FAT filesystem does. Maximum length of a directory is 256 MB (8 M enrtries) while 2 MB at FAT filesystem. The first byte of each entry indicates the type of the entry. It is divided into four bit fields as shown below
Field | Meaning |
---|---|
InUse(bit7) | 0 = Free entry, 1 = Entry is in-use. |
TypeCategory(bit6) | 0 = Primary entry, 1 = Secondary entry attached to primary entry. |
TypeImportance(bit5) | 0 = Critcal entry, 1 = Benign entry. |
TypeCode(bit4-0) | 0-31: Type code. |
Therefore the EntryType byte provides the information about the entry, but in fact the inportant thing is its value to identify the entry type. Following table shows the defined value and entry type. Last four entry types in the table are optional for generic use or not defined for exFAT 1.00, so that they are not described in this documentation.
EntryType | Description |
---|---|
0x81 | Allocation bitmap |
0x82 | Up-case table |
0x83 | Volume label |
0x85 | File and directory (file attribute and timestamp) |
0xC0 | Stream extension (file allocation information) |
0xC1 | File name (name of the file) |
0xC2 | Windows CE access control list |
0xA0 | Volume GUID |
0xA1 | TexFAT Padding |
0xA2 | Windows CE access control table |
EntryType is the only common field for every type of entries and other fields are defined for each entry types individually. To delete an entry, clear InUse bit of EntryType instead of storeing 0xE5 what FAT filessytem does. If EntryType is zero, all following enrties in the directory are guaranteed zero.
Contains allocation information of allocation bitmap. This entry is recorded in the root directory independently.
Field name | Ofs | Size | Function |
---|---|---|---|
EntryType | 0 | 1 | 0x81 (Allocaation bitmap entry). |
BitMapFlags | 1 | 1 | Bit0: 0 = 1st bitmap, 1 = 2nd bitmap. Bit7-1: Reserved (0). |
Reserved | 2 | 18 | Reserved (0). |
FirstCluster | 20 | 4 | First cluster number of this allocation bitmap. |
DataLength | 24 | 8 | Size of bitmap in unit of byte. |
Contains allocation information of up-case table. This entry is recorded in the root directory independently.
Field name | Ofs | Size | Function |
---|---|---|---|
EntryType | 0 | 1 | 0x82 (Up-case table entry). |
Reserved1 | 1 | 3 | Reserved (0). |
TableChecksum | 4 | 4 | 32-bit checksum of the up-case table. |
Reserved2 | 8 | 12 | Reserved (0). |
FirstCluster | 20 | 4 | First cluster number of the up-case table. |
DataLength | 24 | 8 | Size of the table in unit of byte. |
Contains volume label of this volume. This entry is recorded in the root directory independently. If the size field is zero or this entry is not exist, the volume has no volume label. Any character allowed for file name includes dot can be used for the volume label.
Field name | Ofs | Size | Function |
---|---|---|---|
EntryType | 0 | 1 | 0x83 (Volume label entry). |
CharacterCount | 1 | 1 | Size of volume label in unit of character 0 to 11 inclusive. |
VolumeLabel | 2 | 22 | Volume label string in UTF-16LE. Unlike FAT filesystem, case information is preserved and trailing spaces are valid as a part of the label. |
Reserved | 24 | 8 | Reserved (0). |
This is one of the entry types which composes an entry set. It indicates start of an entry set and contains file attribute and timestamp. The entry set is a block of consecutive directory entries to record meta data of a file or sub-directory. It is set on the direcotry in order of this entry (1 entry), stream extension entry (1 entry), and name extensiton entries (1-17 entries), so that an entry set spans from 3 to 19 entries.
Field name | Ofs | Size | Function |
---|---|---|---|
EntryType | 0 | 1 | 0x85 (File and directory entry). |
SecondaryCount | 1 | 1 | Number of following entries. The size of this entry set is SecondaryCount+1 entries. |
SetCheckSum | 2 | 2 | A 16-bit checksum to validate this entry set. The checksum of an entry set is calculated in byte-by-byte as a simple byte array excluding only this field. |
FileAttribute | 4 | 2 | File attribute in combination of following flags. The meaning of these flags are same as FAT filesystem. bit0: Read-Only. bit1: Hidden. bit2: System. bit3: Reserved (0). bit4: Directory. bit5: Archive. bit6-15: Reserved (0). |
Reserved1 | 6 | 2 | Reserved (0). |
CreateTimestamp | 8 | 4 | Timestamp of the file created. Upper 16 bits contains the local date and lower 16 bits contains the local time. Each fields in the date and time are same as FAT filesystem. |
LastModifiedTimestamp | 12 | 4 | Timestamp of the file modified. |
LastAccessedTimestamp | 16 | 4 | Timestamp of the file accessed. |
Create10msIncrement | 20 | 1 | Sub-second information of CreateTimestamp in unit of 10 ms (from 0 to 199). |
LastModified10msIncrement | 21 | 1 | Sub second information of LastModifiedTimestamp. |
CreateTZOffset | 22 | 1 | Time zone offset of CreateTimestamp in unit of quarter hour with ORed by 0x80. For example, +9 * 4 | 0x80 = 0xA4 for JST(+9:00), -7 * 4 | 0x80 = 0xE0 for PST(-7:00). The timestamp in UTC can be got by this field. Set 0x00 when time zone is not used. |
LastModifiedTZOffset | 23 | 1 | Timezone offset of LastModifiedTimestamp. |
LastAccessedTZOffset | 24 | 1 | Timezone offset of LastAccessedTimestamp. |
Reserved2 | 25 | 7 | Reserved (0). |
This is one of the entry types which composes an entry set. It contains the file allocation information.
Field name | Ofs | Size | Function |
---|---|---|---|
EntryType | 0 | 1 | 0xC0 (Stream extension entry). |
GeneralSecondaryFlags | 1 | 1 | These flags indicate status of the file allocation. bit0(AllocationPossible): 0 = Cluster allocation is not possible and FirstCluster and DataLength field are undefined, 1 = Cluster allocation is possible and FirstCluster and DataLength field are valid as defined. bit1(NoFatChain): 0 = Cluster chain on the FAT is valid, 1 = Cluster chain is contiguous and not recorded on the FAT. bit2-15: Reserved (0). If AllocationPossible flag is 0 (actually always 1), ValidDataLength, FirstCluster, DataLength are invalid. When cluster chain is contiguous, NoFatChain can be set and any recording on the FAT is not needed. |
Reserved1 | 2 | 1 | Reserved (0). |
NameLength | 3 | 1 | Length of the file name in UTF-16 encoding unit. Valid value for this field is 1 to 255. |
NameHash | 4 | 2 | 16-bit checksum of the up-case converted file name. The value is calculated in byte-by-byte as a UTF-16LE string. This enables to skip comparison at most unmatched file when look for a file in the directory. |
Reserved2 | 6 | 2 | Reserved (0). |
ValidDataLength | 8 | 8 | Valid data length of the file in unit of byte. It indicates how long the data actually written. Valid value for this field is 0 to DataLength. Any data located beyond this offset is undefined and zeros should be returned if read it. This is the feature to implement fallocate() efficiently. As for the sub-directories, the value must be equal to DataLength. |
Reserved3 | 16 | 4 | Reserved (0). |
FirstCluster | 20 | 4 | First cluster number of the cluster chain. When DataLength is zero, also this value must be zero and the file does not have cluster chain. |
DataLength | 24 | 8 | Actual length of the file in unit of byte. Size of sub-directory is always multiple of cluster size. |
This is one of the entry types which composes an entry set. It contains the file name string. Any character except for control characters (U+0000 to U+001F, U+007F) and " * / : < > ? \ | is allowable for file name as LFN extensiton of FAT filesystem. exFAT filesystem does not support alternative file name (SFN) what FAT filesystem supports.
Field name | Ofs | Size | Function |
---|---|---|---|
EntryType | 0 | 1 | 0xC1 (File name enrty). |
GeneralSecondaryFlags | 1 | 1 | Always 0. |
FileName | 2 | 30 | Contains file name string in UTF-16LE. If the name length is less than 15 characters, rest of space in this field is filled with zero. If the name length exceeds 15 characters, multiple file entries ((NameLength + 14) / 15) are used and set in the entry block in ascending order. |
This is an example of exFAT root directory after formatting and a file creation. You will able to see how the entries are stored on the directory.
To create a file, find a block of free entries in the directory and create the entry set of the file. The entry set has Archive attrribute and set DataLength field to zero as initial value. AllocationPossible bit is always 1 and NoFatChain bit is initially cleared.
When any data is written to the file and a new cluster is allocated, the cluster number is set to FirstCluster and set NoFatChain bit. Thereafter no data is written to the FAT entries as long as the cluter chain is contiguous. When the cluster chain gets fragmented on a cluster allocation, create valid cluster chain on the FAT and clear NoFatChain bit. This is applied to only file and sub-directory. Any other data (root directory, allocation bitmap and up-case table) always have valid cluster chain on the FAT.
To create a sub-directory, find a block of free entries in the directory and create the entry set of the sub-directory. The entry set has Directory attrribute. A cluster is initially allocated to the directory and each entry is filled with zero. NoFatChain bit is as described above. When the sub-directory gets full, the cluster chain is stretched a cluster a time and the cluster is initialized to zero. The maximum length of a direcotry is 256 MB (8 M entries).
There are significant difference from FAT sub-directory. DataLength field has a valid value. Dot entries (".", "..") on the FAT sub-directory are not exist on the exFAT sub-directory. It may be logically generated and appeared in the directory listing.
To remove a file, clear InUse bit in each entries of the file entry set. If the file has a cluster chain, also the chain needs to be removed from the FAT. In this case the FAT entries do not need to be changed because the clusters get free by clearing the allocation bitmap.
It is same as deleting a file. All nodes below the directory needs to be scanned and all files and directories in the directory need to be deleted prior to delete the directory, or those objects' clusters get lost clusters.
Same as the description of FAT filesystem. The value of system type in the partition table for exFAT volume is 0x07, which is same as NTFS. However MBR format does not support the storage device larger than 2 TB, because the allocation information in the partition table is recorded in 32-bit LBA. To use such storage device, it needs to be in SFD format or GPT format.