NAME

Sys::Export::VFAT::Geometry - Calculate addresses and sizes of structures within a FAT filesystem

SYNOPSIS

From an existing filesystem:

open my $fh, '<', '/dev/sda1';
local $/= 4096;
my $boot_sector= <$fh>;
my $geom= Sys::Export::VFAT::Geometry->unpack($boot_sector);

Calculate a filesystem sized to hold N clusters of data:

my $geom= Sys::Export::VFAT::Geometry->new(
  bytes_per_Sector => 512,
  sectors_per_cluster => 8,
  fat_count => 1,
  align_clusters => 4096,
  cluster_count => 12345,
);

DESCRIPTION

The goal of Sys::Export::VFAT is to be able to create filesystems where you have dictated the location or alignment of specific files within the filesystem. Since it is fairly difficult to calculate this, it helps to be able to iterate through different filesystem size parameters until one is found that meets your specification. This module allows you to construct a theoretical filesystem based on your parameters, and then query how the clusters line up with the absolute byte offsets you care about.

This module can also pack and unpack these parameters from a boot sector.

Note that all the defaults in this module aim for minimum-sized read-only FAT filesystems, not the sensible defaults that would provide free space to add new files at runtime.

The math within is derived from the official published formulas in

Microsoft Extensible Firmware Initiative
FAT32 File System Specification
FAT: General Overview of On-Disk Format
Version 1.03, December 6, 2000
Microsoft Corporation

CONSTRUCTORS

new

$geom= Sys::Export::VFAT::Geometry->new(%options);

There are two officially supported sets of options. The first is when you know the geometry parameters from the boot sector of an existing filesystem:

bytes_per_sector
sectors_per_cluster
reserved_sector_count
fat_count
fat_sector_count
root_dirent_count
total_sector_count

The second is when you want to to choose parameters for a new filesystem to hold a known number of clusters:

cluster_count

The desired count of usable data clusters for file and directory data. This may be rounded upward slightly if it is near a FAT16/FAT32 cutoff.

exact_cluster_count

Set this to a true value if you want to prevent rounding upward to a larger bit filesystem for a cluster_count near the boundary.

Without any further options, you will get 512 byte sectors, 4K clusters, 2 allocation tables, 512 root entries (for FAT12/16), and clusters aligned to 4K. You may also specify any of the following options to override those defaults:

volume_offset

If you know that this logical volume starts at a nonzero device address, specify this to get alignments from the start of the device rather than alignments from the start of the volume.

align_clusters

Request power-of-2 alignment of device addresses of clusters. This number is in bytes. If the number is larger than the size of a cluster, it ensures that at least every Nth cluster is aligned. If the number is equal or smaller than the size of a cluster, it ensures that every cluster has that alignment.

For instance, if you set this to 4096, and the volume_offset is 512*3 (which would normally be a poor choice for partition alignment), and the cluster size is also 4096, then every cluster will have a device address with 0 in the low 12 bits, while having a volume offset ending with 512*5 in the low 12 bits.

min_bits

One of 12, 16, or 32. Note that setting this forces a minimum cluster_count, because the selection of FAT bits is based on number of clusters. For instance, FAT32 cannot have fewer than 65525 clusters, which is at least 32MB.

bytes_per_sector

The default is 512.

sectors_per_cluster

The default is 4096 / bytes_per_sector.

fat_count

Only one allocation table is required - the rest are backups in case a sector of the first one is unreadable. The default is 2, which is more common/compatible (but wastes space if you don't need to worry about media errors)

reserved_sector_count

This allocates extra sectors at the start of the volume. It must be at least 1 for the boot sector, and higher on FAT32 for the additional free list and boot sector backup copy.

used_root_dirent_count

If you happen to know the exact number of directory entries of your root directory (including any long filename entries) you can set this to get automatic minimal sizing of the root directory.

unpack

$geom= Sys::Export::VFAT::Geometry->unpack($scalar_or_scalar_ref, %options);

This reads the geometry from a FAT boot sector. The scalar must be at least the first 512 bytes of the filesystem. You should leave most options un-set so that they derive from the boot sector values, but you might choose to set:

volume_offset

If you know the device offset of the FAT volume, setting this lets you use the various <*_device_offset> attributes and methods accurately.

ATTRIBUTES

volume_offset

The device address at which this volume begins. You must supply this to get meaningful values from the various *_device_* methods.

bytes_per_sector

Number of bytes in one disk sector, must be a power of 2 between 512 and 4096.

sectors_per_cluster

Number of sectors that make one cluster. Must be a power of 2 between 1 and 128.

bytes_per_cluster

bytes_per_sector * sectors_per_cluster

dirent_per_sector

Number of 32-byte directory entries in one sector.

dirent_per_cluster

Number of 32-byte directory entries in one cluster.

bits

FAT12, FAT16, or FAT32, derived from number of clusters

reserved_sector_count

Number of sectors at start of volume before FAT tables begin

reserved_size

reserved_sector_count * bytes_per_sector

fat_count

Number of allocation tables (clones for redundancy)

fat_sector_count

Number of sectors required to hold each FAT (based on number of clusters and bits).

fat_size

fat_sector_count * bytes_per_sector

cluster_count

Total number of clusters available for storage (starting from cluster id 2)

min_cluster_id

Lowest cluster id which can store data. Always 2.

max_cluster_id

Highest cluster id which can store data. cluster_count + 1.

root_dir_start_sector

First sector of the Fat12/Fat16 root directory. (Fat32 stores the root dir in the data area)

root_dir_offset

The byte offset of the Fat12/Fat16 root directory from start of volume.

root_dir_sector_count

Total number of sectors required to hold the root directory entries. 0 for FAT32, which stores the root dir in the clusters with everything else.

root_dir_size

root_dir_sector_count * bytes_per_sector

data_start_sector

Sector offset within the volume where the data clusters begin.

data_start_offset

The byte offset from the start of the volume to the start of the data area.

data_start_sector * bytes_per_sector

data_start_device_offset

The start of the data area as an absolute device address.

data_sector_count

Total number of sectors available for data.

data_limit_sector

Sector number following the final data sector, such that

data_limit_sector - data_start_sector = data_sector_count

data_limit_offset

The byte offset from start of the volume to immediately following the data area.

data_limit_device_offset

The limit of the data area as an absolute device address.

total_sector_count

Total number of sectors in the volume.

total_size

Returns the total size in bytes of the volume. total_sector_count * bytes_per_sector.

METHODS

get_cluster_start_sector

$sector= $geom->get_cluster_start_sector($cluster_id);

Return the sector (from start of volume) where the cluster begins. Croaks if you pass an invalid cluster ID.

get_cluster_offset

Same as get_cluster_start_sector but as bytes from start of volume.

get_cluster_device_offset

Same as get_cluster_start_sector but as an absolute device address based on "volume_offset".

get_cluster_of_sector

$cl= $geom->get_cluster_of_sector($sector_idx);

For a sector index (from the start of the volume), return which cluster, if any, contains that sector. Returns undef if it doesn't fall within a cluster.

get_cluster_of_offset

Same as get_cluster_of_sector but from a volume byte offset.

get_cluster_of_device_offset

Same as get_cluster_of_sector but from an absolute device address based on "volume_offset".

get_cluster_extent_of_volume_extent

($cl_start, $cl_count)= $geom->get_cluster_extent_of_volume_extent($vol_offset, $size);

The caller supplies a range of bytes as an absolute offset from the start of the volume and a size in bytes. The offset must also match the start of a cluster, or this dies. Size does not need to end at a cluster boundary. This returns the starting cluster number count of clusters occupied (rounding up). It dies if this range overflows the available clusters.

get_cluster_extent_of_device_extent

Like get_cluster_extent_of_volume_extent but specifies the byte range in terms of the device, factoring in "volume_offset".

get_cluster_alignment_of_device_alignment

($mult, $offset)= $geom->get_cluster_alignment_of_device_alignment($align);

The caller supplies a power-of-2 device alignment (such as 4096), and this returns the multiplier and offset for cluster numbers that will land on that device alignment.

This dies if the specified alignment dosn't fall on any cluster boundary.

FAT STRUCTURE

To understand the attributes of this module, it helps to first understand how FAT is structured.

FAT defines a Sector as some number of bytes (generally 512) and then a Cluster as a number of Sectors. The overall image consists of a header ("reserved clusters"), one or more allocation tables, and then the data area. The allocation table is essentially an array of cluster pointers, the optional additional allocation tables are backup copies of the first, and the data area is an array of clusters. The allocation table is sized so that it has one cluster pointer per data-area cluster. This forms a linked list of clusters, so for any file or directory larger than one cluster, you consult that cluster's entry in the allocation table to find the next cluster. All files and directories are rounded up to the cluster size when they are stored. The size of the cluster pointers depends on the total number of clusters in the filesystem, so for a total cluster count that fits in 12 bits you get FAT12, if it fits in 16 bits you get FAT16, and up to 28 bits uses FAT32. This means that if you specifically need "FAT32" for interoperability resons (such as badly written BIOSes), there is a minimum filesystem size of ~32MB, because the selection of 12/16/32 is driven by the cluster count you specify in the header.

The "V" in VFAT refers to the long file name support that Microsoft added with Windows 95. The directory encoding for FAT only has 11 characters available for each file name, with the last 3 interpreted as a file extension separated from the name with a dot. (the dot is not stored) Rather than inventing a new directory entry encoding, in VFAT they store longer file names (or any name not conforming to the 8.3 notation) in one or more hidden directory entries right before the visible one that actually references the file. This is backward compatible, so it applies equally well to all the FAT bit-widths.

The newer exFAT format (not supported by this module) is a completely different format, more similar to modern filesystems which store variable-length directory entries and which describe file data locations with "extents" (offset and length) rather than an awkward linked list of cluster numbers.

VERSION

version 0.004

AUTHOR

Michael Conrad <mike@nrdvana.net>

COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Michael Conrad.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.