NAME
Sys::Export::VFAT - Write minimal FAT12/16/32 filesystems with control over stored file extents
SYNOPSIS
my $dst= Sys::Export::VFAT->new(
filename => $path,
volume_label => 'ESP'
volume_offset => 2048*512, # inform VFAT of your partition layout
);
# Basic files and directories
$dst->add([ file => "README.TXT", "Hello World\r\n" ]);
$dst->add([ file => 'EFI/BOOT/BOOTIA32.EFI', { data_path => $loader }]);
# Request file 'initrd' have its bytes stored at exactly disk offset 0x110000
$dst->add([ file => 'initrd', { data_path => $initrd, device_offset => 0x110000 }]);
# Request file 'vmlinuz' have its bytes stored aligned to 2048 disk address,
# and capture the location that was chosen into $kernel_ofs
$dst->add(
name => 'vmlinuz',
mode => S_IFREG, # stat constant
data_path => $path_to_kernel,
device_align => 2048,
device_offset => \my $kernel_ofs,
);
$dst->finish;
DESCRIPTION
This module can be used as an export destination to build a FAT32/16/12 filesystem by directly encoding your files into a very compact VFAT layout. The generated filesystem has no fragmentation and no free space (unless you specified device_offset/device_align in a way that created "holes" in your cluster array).
This implementation caches all files in memory, and then chooses FAT parameters that result in the smallest image.
This implementation also has some fun features intended to work together with the ISOHybrid module, which can (on the assumption that the filesystem will never be written) encode hardlinks, encode symlinks as hard-linked directories, and place files at specific offsets within the generated image.
FAT Geometry
This was complicated enough it became its own module. See Sys::Export::VFAT::Geometry.
Algorithm
This module chooses the optimal cluster size and count for the files you provide. That choice affects the starting offset of the first cluster, so this module buffers all directories into memory until the decisions are made, and then writes the whole filesystem in one pass during "finish".
The cluster size/count are chosen by scanning over all the files and directories you have added and total up the number of clusters required, under each of the possible cluster sizes. It also adds in the size of the FAT, which is based on the cluster count. It also calculates a complete assignment of files to clusters, to verify it can meet requests for device_offset or device_align. It then selects whichever successful configuration had the smallest overall size. The calculated cluster assignment is then applied to the files and directory entries, and then the directories get encoded now that the cluster refs are resolved. Finally, each component of the filesystem is written to the destination filename or filehandle.
CONSTRUCTORS
new
$fat= Sys::Export::VFAT->new($filename_or_handle);
$fat= Sys::Export::VFAT->new(%attrs);
$fat= Sys::Export::VFAT->new(\%attrs);
This takes a list of attributes as a hashref or key/value list. If there is exactly one argument, it is treated as the filename attribute.
ATTRIBUTES
filename
Name of file (or device) to write. If the file exists it will be truncated before writing. If you want to write the filesystem amid existing data (like a partition table0, pass a file handle as filehandle.
filehandle
Output filehandle to write. The file will be enlarged if it is not big enough.
root
The root Directory object.
geometry
An instance of Sys::Export::VFAT::Geometry that describes the size and location of VFAT structures. This is generated during "finish", but if you have very rigid ideas about how the filesystem should be laid out, you can pass it to the constructor.
allocation_table
An instance of Sys::Export::VFAT::AllocationTable used to track which clusters have been allocated.
volume_offset
This value causes the entire volume to be written at an offset from the start of the file or device. This value is part of the calculation for methods like "get_file_device_extent" and alignment of files to device addresses. If you are writing this filesystem within a partition of a larger device, set this attribute to get correct device alignments.
If not set, it defaults to 0, so alignments will be performed relative to the start of the volume, and methods like "get_file_device_extent" will return ofsets relative to the volume.
min_bits
Setting this to 32 enforces the generated filesystem will be FAT32 rather than FAT16 or FAT12. FAT32 has a minimum disk size of about 32MiB, so this has the side effect of forcing a minimum number of clusters, which may result in a lot of unused space in the generated filesystem. But, FAT32 is structurally different from FAT12/16 (such as having arbitrary number of reserved sectors at the start of the image, used for things like boot loaders) and you might require that even at the expense of wasted space.
bytes_per_sector
Force a sector size other than the default 512.
sectors_per_cluster
Force a number of sectors per cluster. The default is to try different sizes to see which results in the smallest filesystem.
fat_count
Force a number of allocation tables. Two is standard (for redundancy in case of disk errors) but setting this to 1 saves some space.
free_space
By default, the filesystem is created with zero free clusters. Specify this (in bytes) to add some free space to the generated filesystem.
volume_label
Volume label of the generated filesystem
METHODS
add
$fat->add(\%file_attrs);
# Attributes:
# {
# name => $path_bytes,
# uname => $path_unicode_string,
# FAT_shortname => "8CHARATR.EXT",
# mode => $unix_stat_mode,
# FAT_flags => ATTR_READONLY|ATTR_HIDDEN|ATTR_SYSTEM|ATTR_ARCHIVE,
# atime => $unix_epoch,
# mtime => $unix_epoch,
# btime => $unix_epoch,
# size => $data_size,
# data => $literal_data_or_scalarref,
# device_offset => $desired_byte_offset,
# device_align => $desired_byte_alignment_pow2,
# }
This add method takes the same file objects as used by Sys::Export, but with some optional extras:
- FAT_shortname
-
Any file name not conforming to the 8.3 name limitation of FAT will get an auto-generated "short" filename, in addition to its "long" filename. If you want control over what short name is generated, you can specify it with
FAT_shortname. - FAT_attrs
-
An ORed combination of "ATTR_READONLY", "ATTR_HIDDEN", "ATTR_SYSTEM", or "ATTR_ARCHIVE". If this value is defined, this module will *not* use the
modeuser-write bit to determineATTR_READONLYand will not use leading "." to determineATTR_HIDDEN. - device_offset
-
For integration with ISOHybrid, you may specify
device_offsetto request the file be placed at an exact location, and as a single un-fragmented extent. This accounts for the "device_offset" of the whole filesystem. If you did not set that attribute, this becomes a byte offset from the start of this filesystem.This offset must fall on the address of one of the clusters of the data region, and will generate an exception if it can't be honored. It must also agree with any
device_alignyou requested on other files. Unfortunately you won't get that exception until "finish" is called, as this module looks for workable cluster layouts.You may also set this to a scalar-ref which will receive the device_offset once the file's location is decided.
- device_align
-
Like
device_offset, but if you just want to request the file be aligned to the device rather than needing it to exist at a specific offset. This is a power of 2 in bytes, such as '2048'. This takes attribute "volume_offset" into account, possibly providing alignment that is a multiple of a power-of-2 from the start of the device but not from the start of the volume.
finish
This method performs all the actual work of building the filesystem. This module writes the entire filesystem in one pass after deciding the best geometry and minimal number of clusters to hold the data you've supplied.
You may get exceptions during this call if there isn't a way to write your files as requested.
EXPORTS
is_valid_longname
$bool= is_valid_longname($name)
$name should be a unicode string
is_valid_shortname
$bool= is_valid_shortname($name)
$name should be encoded as platform-native bytes, with no codepoints above 0xFF. Allows space characters in the name, even though most DOS tools can't handle that.
is_valid_volume_label
$bool= is_valid_volume_label($name)
$name should be encoded as platform-native bytes, with no codepoints above 0xFF.
remove_invalid_shortname_chars
$name= remove_invalid_shortname_chars($name, $replacement='_')
Coerce an arbitrary string to characters valid as a FAT short name, uppercasing any lowercase ASCII and replacing illegal characters with '_' or the character of your choice.
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.