pluto/src/kernel/filesystem/fat32.zig
DrDeano 45f37dc3c1
Added read functionality to FAT32
Create test FAT32 image on build
Moved test files to test_files directory
Removed global array for test fat32
Part of 
Removed hard coded test files


Use @embedFile for large file test
2020-12-12 22:31:42 +00:00

3928 lines
146 KiB
Zig
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const std = @import("std");
const builtin = @import("builtin");
const expectEqualSlices = std.testing.expectEqualSlices;
const expectEqual = std.testing.expectEqual;
const expectError = std.testing.expectError;
const expect = std.testing.expect;
const log = std.log.scoped(.fat32);
const AutoHashMap = std.AutoHashMap;
const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;
const vfs = @import("vfs.zig");
const mem = @import("../mem.zig");
const CodePage = @import("../code_page/code_page.zig").CodePage;
const mkfat32 = @import("../../../mkfat32.zig");
/// The boot record for FAT32. This is use for parsing the initial boot sector to extract the
/// relevant information for todays FAT32.
const BootRecord = struct {
/// The jump bytes that begin the boot code. This will jump over the FAT32 header.
jmp: [3]u8,
/// The OEM name. This is a 8 character string padded by spaces.
oem: [8]u8,
/// The number of bytes per sector.
bytes_per_sector: u16,
/// The number of sectors per cluster.
sectors_per_cluster: u8,
/// The number of reserved sectors at the beginning of the image. This is where the fat
/// header, FSInfo and FAT is stored.
reserved_sectors: u16,
/// The number of FAT's.
fat_count: u8,
/// The size in bytes of the root directory. This is only used by FAT12 and FAT16, so is
/// always 0 for FAT32.
root_directory_size: u16,
/// The total number of sectors. This is only used for FAT12 and FAT16, so is always 0 for
/// FAT32.
total_sectors_12_16: u16,
/// The media type. This is used to identify the type of media.
media_descriptor_type: u8,
/// The total number of sectors of the FAT. This is only used for FAT12 and FAT16, so
/// always 0 for FAT32.
sectors_per_fat_12_16: u16,
/// The number of sectors per track.
sectors_per_track: u16,
/// The number of heads.
head_count: u16,
/// The number of hidden sectors.
hidden_sectors: u32,
/// The total number of sectors for FAT32.
total_sectors: u32,
/// The number of sectors the FAT takes up for FAT32.
sectors_per_fat: u32,
/// Mirror flags.
mirror_flags: u16,
/// The version.
version_number: u16,
/// The start cluster of the root directory.
root_directory_cluster: u32,
/// The sector where is the FS information sector is located. A value of 0x0000 or 0xFFFF
/// indicates that there isn't a FSInfo structure.
fsinfo_sector: u16,
/// The sector for the backup boot record where the first 3 sectors are copied. A value of
/// 0x000 or 0xFFFF indicate there is no backup.
backup_boot_sector: u16,
/// Reserved. All zero.
reserved0: [12]u8,
/// The physical drive number.
drive_number: u8,
/// Reserved. All zero.
reserved1: u8,
/// The extended boot signature.
signature: u8,
/// The serial number of the FAT image at format. This is a function of the current
/// timestamp of creation.
serial_number: u32,
/// The partitioned volume label. This is a 11 character string padded by spaces.
volume_label: [11]u8,
/// The file system type. This is a 8 character string padded by spaces. For FAT32, this is
/// 'FAT32 '
filesystem_type: [8]u8,
// We are ignoring the boot code as we don't need to parse this.
};
/// The FSInfo block. This is used for parsing the initial sector to extract the relevant
/// information: number_free_clusters and next_free_cluster.
const FSInfo = struct {
/// The lead signature: 0x41615252
lead_signature: u32,
/// Reserved bytes
reserved0: [480]u8,
/// The middle or struct signature: 0x61417272
struct_signature: u32,
/// The number of free clusters in the image
number_free_clusters: u32,
/// The next available free cluster that can be allocated for writing to.
next_free_cluster: u32,
/// Reserved bytes
reserved1: [12]u8,
/// The tail signature: 0xAA550000
tail_signature: u32,
};
/// A long name entry. This is part of a fill long name block, but each entry is 32 bytes long.
/// Entries are ordered backwards. The file name is encoded as a wide UTF16 characters.
const LongName = struct {
/// The order in the long entry sequence. If OR'ed with 0x40, then this is the last entry.
order: u8,
/// The first 5 wide characters in the block.
first: [5]u16,
/// The attributes from the short name block so will always be 0x0F as this identifies as a
/// long entry.
attribute: u8 = 0x0F,
/// Always 0x00, other values are reserved. 0x00 Means this is a sub-component of the long name
/// entry, i.e. there are multiple blocks that make up the long entry.
long_entry_type: u8 = 0x00,
/// The check sum of the 11 character short file name that goes along with this long name.
check_sum: u8,
/// The next 6 wide characters in the block.
second: [6]u16,
/// Must be zero, as this is an artifact from the short name entry to be compatible with
/// systems that don't support long name entries.
zero: u16 = 0x0000,
/// The last 2 wide characters in the block.
third: [2]u16,
/// This is the error set for std.unicode.Utf16LeIterator.
const Error = error{
UnexpectedSecondSurrogateHalf,
ExpectedSecondSurrogateHalf,
DanglingSurrogateHalf,
};
///
/// Given a long name entry part, get the name associated with this where the UFT16 encoded
/// characters are converted to UTF8. This will exclude the NULL terminator. If an error
/// occurs, then should fail the long name parsing, or be treated as an orphan file.
///
/// Arguments:
/// IN self: *const LongName - The long name entry to get the name from.
/// IN buff: []u8 - A buffer to copy the name into.
///
/// Return: u32
/// The index into the buffer where the last character was stored.
///
/// Error: std.unicode.Utf16LeIterator
/// An error when parsing the wide UFT16 characters in the long name.
///
pub fn getName(self: *const LongName, buff: []u8) Error!u32 {
// No error can happen when encoding to UFT8 as will be getting valid code points from the
// UTF16 iterator
var index: u8 = 0;
var f_i = std.unicode.Utf16LeIterator.init(self.first[0..]);
while (try f_i.nextCodepoint()) |code_point| {
// long names are null terminated, so return
if (code_point == 0) {
return index;
}
index += std.unicode.utf8Encode(code_point, buff[index..]) catch unreachable;
}
var s_i = std.unicode.Utf16LeIterator.init(self.second[0..]);
while (try s_i.nextCodepoint()) |code_point| {
// long names are null terminated, so return
if (code_point == 0) {
return index;
}
index += std.unicode.utf8Encode(code_point, buff[index..]) catch unreachable;
}
var t_i = std.unicode.Utf16LeIterator.init(self.third[0..]);
while (try t_i.nextCodepoint()) |code_point| {
// long names are null terminated, so return
if (code_point == 0) {
return index;
}
index += std.unicode.utf8Encode(code_point, buff[index..]) catch unreachable;
}
return index;
}
};
/// A short name entry. This is the standard FAT32 file/directory entry. Each entry is 32 bytes
/// long. The file name is encoded in code page 437. When creating a short name, this will be in
/// ASCII as there isn't enough support in the std lib for unicode uppercase. But when parsing,
/// will decode proper code page 437 characters.
const ShortName = struct {
/// The short name. All uppercase encoded in code page 437.
name: [8]u8,
/// The extension of the file. All uppercase encoded in code page 437.
extension: [3]u8,
/// The file attributes. See Attributes for the options. The upper 2 bits are reserved.
attributes: u8,
/// Reserved for Windows NT.
reserved: u8 = 0x00,
/// The 10th of a second part of the time created. The granularity of time created is 2
/// seconds, so this can range from 0-199.
time_created_tenth: u8,
/// The time of creation.
time_created: u16,
/// The date of creation.
date_created: u16,
/// The date of last read or write.
date_last_access: u16,
/// The higher word of the entries first cluster.
cluster_high: u16,
/// Time of last modification.
time_last_modification: u16,
/// Date of last modification.
date_last_modification: u16,
/// The lower word of the entries first cluster.
cluster_low: u16,
/// The real file size in bytes.
size: u32,
/// The file attributes
const Attributes = enum(u8) {
/// A normal read/write file, not hidden, not a system file, don't backup.
None = 0x00,
/// Read only, can't write to the file.
ReadOnly = 0x01,
/// A normal directory listing won't show this. Can still access this as a normal file.
Hidden = 0x02,
/// An operating system file.
System = 0x04,
/// The real volume ID. There should only be one of these and in the root directory. The
/// cluster should be set to 0.
VolumeID = 0x08,
/// A directory, not a file.
Directory = 0x10,
/// Indicates that a file/directory is to be achieved/backed up but backup utilities.
Archive = 0x20,
};
///
/// Get the short name from the entry. This will be encoded in UFT8. This will also include a
/// '.' between the name and extension and ignore trailing spaces.
///
/// Arguments:
/// IN self: *const ShortName - The short name entry to get the name from.
/// IN buff: []u8 - A buffer to copy the UTF8 encoded name.
///
/// Return: u32
/// The index into the buffer where the last character was stored.
///
pub fn getName(self: *const ShortName, buff: []u8) u32 {
var index: u8 = 0;
for (self.name) |char| {
if (char != ' ') {
// 0x05 is actually 0xE5
if (char == 0x05) {
const utf16 = CodePage.toWideChar(.CP437, 0xE5);
// The code page will return a valid character so the encode won't error
index += std.unicode.utf8Encode(utf16, buff[index..]) catch unreachable;
} else {
const utf16 = CodePage.toWideChar(.CP437, char);
// The code page will return a valid character so the encode won't error
index += std.unicode.utf8Encode(utf16, buff[index..]) catch unreachable;
}
} else {
break;
}
}
if (!self.isDir()) {
buff[index] = '.';
index += 1;
for (self.extension) |char| {
if (char != ' ') {
const utf16 = CodePage.toWideChar(.CP437, char);
// The code page will return a valid character so the encode won't error
index += std.unicode.utf8Encode(utf16, buff[index..]) catch unreachable;
} else {
break;
}
}
}
return index;
}
///
/// Get the original short file name without encoding into UTF8.
///
/// Arguments:
/// IN self: *const ShortName - The short name entry to get the SFN name from.
///
/// Return: [11]u8
/// The original 11 characters of the short name entry.
///
pub fn getSFNName(self: *const ShortName) [11]u8 {
var name: [11]u8 = [_]u8{' '} ** 11;
std.mem.copy(u8, name[0..], self.name[0..]);
std.mem.copy(u8, name[8..], self.extension[0..]);
return name;
}
///
/// Check the attributes and check if the entry is a directory.
///
/// Arguments:
/// IN self: *const ShortName - The short name entry to get if this is a directory.
///
/// Return: bool
/// Whether the file is a directory or not.
///
pub fn isDir(self: *const ShortName) bool {
return self.attributes & @enumToInt(ShortName.Attributes.Directory) == @enumToInt(ShortName.Attributes.Directory);
}
///
/// Get the full first cluster number by combining the upper and lower parts of the cluster
/// number.
///
/// Arguments:
/// IN self: *const ShortName - The short name entry to get the cluster from.
///
/// Return: u32
/// The first cluster of the file.
///
pub fn getCluster(self: *const ShortName) u32 {
return @as(u32, self.cluster_high) << 16 | self.cluster_low;
}
///
/// Calculate the check sum for the long name entry.
///
/// Arguments:
/// IN self: *const ShortName - The short entry to calculate the check sum for.
///
/// Return: u8
/// The check sum.
///
pub fn calcCheckSum(self: *const ShortName) u8 {
var check_sum: u8 = 0;
// This is dumb, the check sum relies on the wrap around >:(
for (self.name) |char| {
check_sum = (check_sum << 7) +% (check_sum >> 1) +% char;
}
for (self.extension) |char| {
check_sum = (check_sum << 7) +% (check_sum >> 1) +% char;
}
return check_sum;
}
};
/// A complete FAT entry. This includes a list of long entries and one short entry.
const FatDirEntry = struct {
/// The list of long entries. This will be in reverse order.
long_entry: []LongName,
/// The short entry.
short_entry: ShortName,
};
/// The FAT32 configuration for modern FAT32. This so to remove the redundant FAT12/16
/// aspect of the boot sector.
const FATConfig = struct {
/// The number of bytes per sector. This will be normally 512, but will depend on the
/// hardware.
bytes_per_sector: u16,
/// The number of sectors per cluster. This will depend on the size of the disk.
sectors_per_cluster: u8,
/// The number of reserved sectors at the beginning of the filesystem. This is normally
/// 32 sectors.
reserved_sectors: u16,
/// The number of hidden sectors. This is relevant for partitioned disks.
hidden_sectors: u32,
/// The total number of sectors of the filesystem.
total_sectors: u32,
/// The number of sectors that are occupied by the FAT.
sectors_per_fat: u32,
/// The cluster number of the root directory.
root_directory_cluster: u32,
/// The sector of the FSInfo sector.
fsinfo_sector: u16,
/// The sector number of the backup sector.
backup_boot_sector: u16,
/// If the filesystem has a FSInfo block.
has_fs_info: bool,
/// The number of free clusters on the disk. Will be 0xFFFFFFFF is unknown.
number_free_clusters: u32,
/// The next free cluster to start looking for when allocating a new cluster for a
/// file. Will be 0xFFFFFFFF is unknown.
next_free_cluster: u32,
/// The end marker when reading the cluster chain. This will be in range from
/// 0x0FFFFFF8 - 0x0FFFFFFF
cluster_end_marker: u32,
///
/// Convert a cluster to the corresponding sector from the filesystem's configuration.
///
/// Arguments:
/// IN self: *const FATConfig - The FAT32 configuration.
/// IN cluster: u32 - The cluster number to convert.
///
/// Return: u32
/// The sector number.
///
pub fn clusterToSector(self: *const FATConfig, cluster: u32) u32 {
// FAT count will be 2 as this is checked in the init function
return (self.sectors_per_fat * 2) + self.reserved_sectors + ((cluster - 2) * self.sectors_per_cluster);
}
};
///
/// Initialise a struct from bytes.
/// TODO: Once packed structs are good to go, then use std.mem.bytesAsValue.
///
/// Arguments:
/// IN comptime Type: type - The struct type to initialise
/// IN bytes: []const u8 - The bytes to initialise the struct with.
///
/// Return: Type
/// The struct initialised with the bytes.
///
fn initStruct(comptime Type: type, bytes: []const u8) Type {
var ret: Type = undefined;
comptime var index = 0;
inline for (std.meta.fields(Type)) |item| {
switch (item.field_type) {
u8 => @field(ret, item.name) = bytes[index],
u16 => @field(ret, item.name) = std.mem.bytesAsSlice(u16, bytes[index .. index + 2])[0],
u32 => @field(ret, item.name) = std.mem.bytesAsSlice(u32, bytes[index .. index + 4])[0],
else => {
switch (@typeInfo(item.field_type)) {
.Array => |info| switch (info.child) {
u8 => {
comptime var i = 0;
inline while (i < info.len) : (i += 1) {
@field(ret, item.name)[i] = bytes[index + i];
}
},
u16 => {
comptime var i = 0;
inline while (i < info.len) : (i += 1) {
@field(ret, item.name)[i] = std.mem.bytesAsSlice(u16, bytes[index + (i * 2) .. index + 2 + (i * 2)])[0];
}
},
else => @compileError("Unexpected field type: " ++ @typeName(info.child)),
},
else => @compileError("Unexpected field type: " ++ @typeName(item.field_type)),
}
},
}
index += @sizeOf(item.field_type);
}
return ret;
}
///
/// A convenient function for returning the error types for reading, writing and seeking a stream.
///
/// Arguments:
/// IN comptime StreamType: type - The stream to get the error set from.
///
/// Return: type
/// The Error set for reading, writing and seeking the stream.
///
fn ErrorSet(comptime StreamType: type) type {
const ReadError = switch (@typeInfo(StreamType)) {
.Pointer => |p| p.child.ReadError,
else => StreamType.ReadError,
};
const WriteError = switch (@typeInfo(StreamType)) {
.Pointer => |p| p.child.WriteError,
else => StreamType.WriteError,
};
const SeekError = switch (@typeInfo(StreamType)) {
.Pointer => |p| p.child.SeekError,
else => StreamType.SeekError,
};
return ReadError || WriteError || SeekError;
}
///
/// FAT32 filesystem.
///
/// Arguments:
/// IN comptime StreamType: type - The type of the stream to be used as the underlying device.
///
/// Return: type
/// The FAT32 filesystem that depends on the stream type.
///
pub fn Fat32FS(comptime StreamType: type) type {
return struct {
/// The underlying virtual filesystem
fs: *vfs.FileSystem,
/// An allocator for allocating memory for FAT32 operations.
allocator: *Allocator,
/// The root node of the FAT32 filesystem.
root_node: RootNode,
/// The configuration for the FAT32 filesystem.
fat_config: FATConfig,
/// A mapping of opened files so can easily retrieved opened files for reading, writing and
/// closing.
opened_files: AutoHashMap(*const vfs.Node, *OpenedInfo),
/// The underlying hardware device that the FAT32 filesystem will be operating on. This could
/// be a ramdisk, hard drive, memory stick...
stream: StreamType,
/// See vfs.FileSystem.instance
instance: usize,
/// The root node struct for storing the root of the filesystem.
const RootNode = struct {
/// The VFS node of the root directory.
node: *vfs.Node,
/// The cluster number for the root directory.
cluster: u32,
};
/// The struct for storing the data needed for an opened file or directory.
const OpenedInfo = struct {
/// The cluster number of the file or directory.
cluster: u32,
/// The real size of the file. This will be zero for directories.
size: u32,
};
/// The error set for the FAT32 filesystem.
const Error = error{
/// If the boot sector doesn't have the 0xAA55 signature as the last word. This would
/// indicate this is not a valid boot sector.
BadMBRMagic,
/// An unexpected filesystem type. The filesystem type must be FAT32.
BadFSType,
/// An unexpected media descriptor other than 0xF8.
BadMedia,
/// An unexpected extended BIOS block signature other than 0x29.
BadSignature,
/// An unexpected FAT32 configuration. This will be if there are values set in the boot
/// sector that are unexpected for FAT32.
BadFat32,
/// An unexpected FAT count. There should only be 2 tables.
BadFATCount,
/// An unexpected flags. Should be set to mirror which is zero.
NotMirror,
/// An unexpected root cluster number. Should be 2.
BadRootCluster,
/// When reading from the stream, if the read count is less than the expected read,
/// then there is a bad read.
BadRead,
/// When creating a new FAT32 entry, if the name doesn't match the specification.
InvalidName,
};
/// The internal self struct
const Fat32Self = @This();
// Can't directly access the fields of a pointer type, idk if this is a bug?
/// The errors that can occur when reading from the stream.
const ReadError = switch (@typeInfo(StreamType)) {
.Pointer => |p| p.child.ReadError,
else => StreamType.ReadError,
};
/// The errors that can occur when writing to the stream.
const WriteError = switch (@typeInfo(StreamType)) {
.Pointer => |p| p.child.WriteError,
else => StreamType.WriteError,
};
/// The errors that can occur when seeking the stream.
const SeekError = switch (@typeInfo(StreamType)) {
.Pointer => |p| p.child.SeekError,
else => StreamType.SeekError,
};
/// The errors that can occur when getting the seek position of the stream.
const GetPosError = switch (@typeInfo(StreamType)) {
.Pointer => |p| p.child.GetPosError,
else => StreamType.GetPosError,
};
/// An iterator for looping over the cluster chain in the FAT.
const ClusterChainIterator = struct {
/// The allocator used for allocating the initial FAT array, then to free in deinit.
allocator: *Allocator,
/// The current cluster value.
cluster: u32,
/// The configuration for this FAT instance. Used for converting clusters to sectors
/// and seeking to the correct location to read the next cluster value.
fat_config: FATConfig,
/// The underlying stream to read the FAT.
stream: StreamType,
/// The cached FAT.
fat: []u32,
/// The offset into the FAT. If the next cluster is outside the cached FAT, then will
/// need to read a new part of the FAT.
table_offset: u32,
/// The offset used when reading part of a cluster. This will range from 0 to
/// bytes_per_sector * sectors_per_cluster.
cluster_offset: u32,
/// The iterators self.
const ClusterChainIteratorSelf = @This();
///
/// Check if we need to advance the cluster value and in turn check if the new cluster
/// is within the cached FAT.
///
/// Arguments:
/// IN self: *ClusterChainIteratorSelf - Iterator self.
///
/// Error: Fat32Self.Error || ReadError || SeekError
/// Fat32Self.Error - If reading the stream didn't fill the cache FAT array.
/// ReadError - If there is an error reading from the stream.
/// SeekError - If there is an error seeking the stream.
///
fn checkRead(self: *ClusterChainIteratorSelf) (Fat32Self.Error || ReadError || SeekError)!void {
if (self.cluster_offset >= self.fat_config.bytes_per_sector * self.fat_config.sectors_per_cluster) {
self.cluster = self.fat[self.cluster - self.table_offset];
self.cluster_offset = 0;
// If we are at the end, break
if ((self.cluster & 0x0FFFFFFF) >= self.fat_config.cluster_end_marker) {
return;
}
// This will also allow for forwards and backwards iteration of the FAT
const table_offset = self.cluster / self.fat.len;
if (table_offset != self.table_offset) {
self.table_offset = table_offset;
try self.stream.seekableStream().seekTo((self.fat_config.reserved_sectors + self.table_offset) * self.fat_config.bytes_per_sector);
const read_count = try self.stream.reader().readAll(std.mem.sliceAsBytes(self.fat));
if (read_count != self.fat.len * @sizeOf(u32)) {
return Fat32Self.Error.BadRead;
}
}
}
}
///
/// Iterate the cluster chain of FAT32. This will follow the FAT chain reading the data
/// into the buffer provided where the cluster is pointing to. This will read the
/// entire clusters data into the buffer. If the buffer is full or the end of the
/// cluster chain is reached, then will return null else will return the end index into
/// the buffer to next read into. Currently, this will be the bytes per cluster and
/// only buffers aligned to bytes per cluster will work.
///
/// Arguments:
/// IN self: *ClusterChainIteratorSelf - Self iterator.
/// IN buff: []u8 - The buffer to read the data into.
///
/// Return: ?usize
/// The end index into the buffer where the next read should start. If returned
/// null, then the buffer is full or the end of the cluster chain is reached.
///
/// Error: Fat32Self.Error || ReadError || SeekError
/// Fat32Self.Error - (BadRead) If the buffer isn't aligned to the bytes per cluster boundary.
/// ReadError - If there is an error reading from the stream.
/// SeekError - If there is an error seeking the stream.
///
pub fn read(self: *ClusterChainIteratorSelf, buff: []u8) (Fat32Self.Error || ReadError || SeekError)!?usize {
// FAT32 is really FAT28, so the top 4 bits are not used, so mask them out
if (buff.len != 0 and self.cluster != 0 and (self.cluster & 0x0FFFFFFF) < self.fat_config.cluster_end_marker and buff.len > 0) {
// Seek to the sector where the cluster is
const sector = self.fat_config.clusterToSector(self.cluster);
try self.stream.seekableStream().seekTo(sector * self.fat_config.bytes_per_sector + self.cluster_offset);
const read_len = std.math.min(buff.len, self.fat_config.bytes_per_sector * self.fat_config.sectors_per_cluster);
self.cluster_offset += read_len;
// Read the cluster
// TODO: Maybe cache bytes per cluster block, then can read into a buffer that isn't aligned to 512 bytes.
// So would read from the cache rather than the stream itself.
const read_count = try self.stream.reader().readAll(buff[0..read_len]);
if (read_count != read_len) {
return Fat32Self.Error.BadRead;
}
// Increment the cluster
// Check if we need to read another part of the FAT
try self.checkRead();
return read_len;
}
return null;
}
///
/// Deinitialise the cluster chain iterator.
///
/// Arguments:
/// IN self: *ClusterChainIteratorSelf - Iterator self.
///
///
pub fn deinit(self: *const ClusterChainIteratorSelf) void {
self.allocator.free(self.fat);
}
///
/// Initialise a cluster chain iterator.
///
/// Arguments:
/// IN allocator: *Allocator - The allocator for allocating a FAT cache.
/// IN fat_config: FATConfig - The FAT configuration.
/// IN cluster: u32 - The first cluster to start reading from.
/// IN stream: StreamType - The underlying stream.
///
/// Return: ClusterChainIteratorSelf
/// A cluster chain iterator.
///
/// Error: Allocator.Error || Fat32Self.Error || ReadError || SeekError
/// Allocator.Error - If there is an error allocating the initial FAT cache.
/// Fat32Self.Error - If reading the stream didn't fill the cache FAT array.
/// ReadError - If there is an error reading from the stream.
/// SeekError - If there is an error seeking the stream.
///
pub fn init(allocator: *Allocator, fat_config: FATConfig, cluster: u32, stream: StreamType) (Allocator.Error || Fat32Self.Error || ReadError || SeekError)!ClusterChainIteratorSelf {
// Create a bytes per sector sized cache of the FAT.
var fat = try allocator.alloc(u32, fat_config.bytes_per_sector / @sizeOf(u32));
errdefer allocator.free(fat);
// FAT32 has u32 entries so need to divide
const table_offset = cluster / (fat_config.bytes_per_sector / @sizeOf(u32));
// Seek to the FAT
// The FAT is just after the reserved sectors + the index
try stream.seekableStream().seekTo((fat_config.reserved_sectors + table_offset) * fat_config.bytes_per_sector);
const read_count = try stream.reader().readAll(std.mem.sliceAsBytes(fat));
if (read_count != fat.len * @sizeOf(u32)) {
return Fat32Self.Error.BadRead;
}
return ClusterChainIteratorSelf{
.allocator = allocator,
.cluster = cluster,
.fat_config = fat_config,
.stream = stream,
.fat = fat,
.table_offset = table_offset,
.cluster_offset = 0,
};
}
};
/// An iterator for looping over the directory structure of FAT32.
const EntryIterator = struct {
/// An allocator for memory stuff
allocator: *Allocator,
/// A cache of the current cluster. This wil be read from the cluster chain iterator.
cluster_block: []u8,
/// The current index into the cluster block.
index: u32,
/// The cluster chain iterator to read the next custer when needed.
cluster_chain: ClusterChainIterator,
/// The entry iterator self.
const EntryIteratorSelf = @This();
/// The entry returned from 'next()'.
pub const Entry = struct {
/// The allocator used to allocate fields of this entry. Also used to free these
/// entries in the 'deinit()' function.
allocator: *Allocator,
/// The long name for the entry. This maybe null as not all entry have a long name
/// part, just a short name.
long_name: ?[]const u8,
/// The short name struct.
short_name: ShortName,
///
/// Free the entry.
///
/// Arguments:
/// IN self: *Entry - The entry self to free.
///
///
pub fn deinit(self: *const Entry) void {
if (self.long_name) |name| {
self.allocator.free(name);
}
}
};
/// The error set for the entry iterator.
const EntryItError = error{
/// If the long entry is invalid, so ignore it and use the short name only.
Orphan,
/// If reading the cluster chain and reach the end unexpectedly.
EndClusterChain,
};
///
/// Check if the next entry will be outside the cluster block cache. If so, read the
/// next cluster and update the index to 0.
///
/// Arguments:
/// IN self: *EntryIteratorSelf - Iterator self.
///
/// Error: Fat32Self.Error || ReadError || SeekError || EntryItError
/// Fat32Self.Error - Error reading the cluster chain if the buffer isn't aligned.
/// ReadError - Error reading from the stream in the cluster iterator.
/// SeekError - Error seeking the stream in the cluster iterator.
/// error.EndClusterChain - Reading the next cluster and reach the end unexpectedly.
///
fn checkRead(self: *EntryIteratorSelf) (Fat32Self.Error || ReadError || SeekError || EntryItError)!void {
if (self.index >= self.cluster_block.len) {
// Read the next block
var index: u32 = 0;
while (try self.cluster_chain.read(self.cluster_block[index..])) |next_index| {
index += next_index;
}
// Didn't read so at end of cluster chain
// TODO: This relies on that cluster chain iterator will return full cluster bytes
// Currently this is the case, but when changed will need to update this to
// include partially full cluster block cache, with an index.
if (index < self.cluster_block.len) {
return EntryItError.EndClusterChain;
}
// Reset the index
self.index = 0;
}
}
///
/// A helper function for reading a entry. This is used for catching orphaned long
/// entries and ignoring them so this shouldn't be called directly.
///
/// Arguments:
/// IN self: *EntryIteratorSelf - Iterator self.
///
/// Return: ?Entry
/// The FAT entry. Will return null if there are no more entries for the directory.
///
/// Error: Allocator.Error || Fat32Self.Error || ReadError || SeekError || EntryItError
/// Allocator.Error - Error allocating memory for fields in the return entry.
/// Fat32Self.Error - Error reading the cluster chain if the buffer isn't aligned.
/// ReadError - Error reading from the underlying stream.
/// SeekError - Error seeking the underlying stream.
/// error.Orphan - If there is a long entry without a short name entry or the
/// check sum in the long entry is incorrect with the associated
/// short entry or the long entry is missing entry parts.
/// error.EndClusterChain - Reading the next cluster and reach the end unexpectedly.
///
fn nextImp(self: *EntryIteratorSelf) (Allocator.Error || Fat32Self.Error || ReadError || SeekError || LongName.Error || EntryItError)!?Entry {
// Do we need to read the next block
try self.checkRead();
// Ignore deleted files
while (self.cluster_block[self.index] == 0xE5) : ({
self.index += 32;
try self.checkRead();
}) {}
// Are we at the end of all entries
if (self.cluster_block[self.index] != 0x00) {
// The long name if there is one
var long_name: ?[]u8 = null;
errdefer if (long_name) |name| self.allocator.free(name);
var long_entries: ?[]LongName = null;
defer if (long_entries) |entries| self.allocator.free(entries);
// If attribute is 0x0F, then is a long file name and
if (self.cluster_block[self.index] & 0x40 == 0x40 and self.cluster_block[self.index + 11] == 0x0F) {
// How many entries do we have, the first byte of the order. This starts at 1 not 0
var long_entry_count = self.cluster_block[self.index] & ~@as(u32, 0x40);
// Allocate a buffer for the long name. 13 for the 13 characters in the long entry
// * 3 as long names are u16 chars so when converted to u8, there could be 3 u8 to encode a u16 (UTF16 -> UTF8)
long_name = try self.allocator.alloc(u8, 13 * 3 * long_entry_count);
// For convenience
var long_name_temp = long_name.?;
var long_name_index: u32 = 0;
long_entries = try self.allocator.alloc(LongName, long_entry_count);
// Iterate through the long name entries
while (long_entry_count > 0) : ({
self.index += 32;
long_entry_count -= 1;
try self.checkRead();
}) {
// Parse the long entry
long_entries.?[long_entry_count - 1] = initStruct(LongName, self.cluster_block[self.index..]);
// Check the order of the long entry as it could be broken
if ((long_entries.?[long_entry_count - 1].order & ~@as(u32, 0x40)) != long_entry_count) {
// A broken long entry
self.index += 32;
return EntryItError.Orphan;
}
}
// Parse the name
for (long_entries.?) |entry| {
long_name_index += try entry.getName(long_name_temp[long_name_index..]);
}
long_name = long_name_temp[0..long_name_index];
}
// Make sure we have a short name part, if not then it is a orphan
// Easy check for the attributes as 0x0F is invalid and a long name part
// So if we have one, then this is a long entry not short entry as expected
// Also make are we are not at the end
if (self.cluster_block[self.index] != 0x00 and self.cluster_block[self.index + 11] == 0x0F) {
// This will be an invalid short name
self.index += 32;
return EntryItError.Orphan;
}
// Parse the short entry
// We need all parts of the short name, not just the name
const short_entry = initStruct(ShortName, self.cluster_block[self.index..]);
// Check the check sum
if (long_entries) |entries| {
for (entries) |entry| {
if (entry.check_sum != short_entry.calcCheckSum()) {
return EntryItError.Orphan;
}
}
}
// Increment for the next entry
self.index += 32;
return Entry{
.allocator = self.allocator,
.long_name = long_name,
.short_name = short_entry,
};
}
return null;
}
///
/// Get the next entry in the iterator. Will return null when at the end. This will
/// ignore orphaned long entries.
///
/// Arguments:
/// IN self: *EntryIteratorSelf - Iterator self.
///
/// Return: ?Entry
/// The FAT entry. Will return null if there are no more entries for the directory.
/// The entry must be free using the deinit() function.
///
/// Error: Allocator.Error || Fat32Self.Error || ReadError || SeekError
/// Allocator.Error - Error allocating memory for fields in the return entry.
/// Fat32Self.Error - Error reading the cluster chain if the buffer isn't aligned.
/// ReadError - Error reading from the underlying stream.
/// SeekError - Error seeking the underlying stream.
///
pub fn next(self: *EntryIteratorSelf) (Allocator.Error || Fat32Self.Error || ReadError || SeekError || LongName.Error)!?Entry {
// If there is a orphan file, then just get the next one
// If we hit the end of the cluster chain, return null
while (true) {
if (self.nextImp()) |n| {
return n;
} else |e| switch (e) {
error.Orphan => continue,
error.EndClusterChain => return null,
else => return @errSetCast(Allocator.Error || Fat32Self.Error || ReadError || SeekError || LongName.Error, e),
}
}
}
///
/// Destroy the entry iterator freeing any memory.
///
/// Arguments:
/// IN self: *EntryIteratorSelf - Iterator self.
///
pub fn deinit(self: *const EntryIteratorSelf) void {
self.allocator.free(self.cluster_block);
self.cluster_chain.deinit();
}
///
/// Initialise a directory entry iterator to looping over the FAT entries. This uses
/// the cluster chain iterator.
///
/// Arguments:
/// IN allocator: *Allocator - The allocator for allocating a cluster block cache.
/// IN fat_config: FATConfig - The FAT configuration.
/// IN cluster: u32 - The first cluster to start reading from.
/// IN stream: StreamType - The underlying stream.
///
/// Return: EntryIteratorSelf
/// The entry iterator.
///
/// Error: Allocator.Error || Fat32Self.Error || ReadError || SeekError
/// Allocator.Error - Error allocating memory for fields in the return entry.
/// Fat32Self.Error - Error reading the cluster chain if the buffer isn't aligned.
/// ReadError - Error reading from the underlying stream.
/// SeekError - Error seeking the underlying stream.
///
pub fn init(allocator: *Allocator, fat_config: FATConfig, cluster: u32, stream: StreamType) (Allocator.Error || Fat32Self.Error || ReadError || SeekError)!EntryIteratorSelf {
var cluster_block = try allocator.alloc(u8, fat_config.bytes_per_sector * fat_config.sectors_per_cluster);
errdefer allocator.free(cluster_block);
var it = try ClusterChainIterator.init(allocator, fat_config, cluster, stream);
errdefer it.deinit();
var index: u32 = 0;
while (try it.read(cluster_block[index..])) |next_index| {
index += next_index;
}
return EntryIteratorSelf{
.allocator = allocator,
.cluster_block = cluster_block,
.index = 0,
.cluster_chain = it,
};
}
};
/// See vfs.FileSystem.getRootNode
fn getRootNode(fs: *const vfs.FileSystem) *const vfs.DirNode {
const self = @fieldParentPtr(Fat32Self, "instance", fs.instance);
return &self.root_node.node.Dir;
}
/// See vfs.FileSystem.close
fn close(fs: *const vfs.FileSystem, node: *const vfs.Node) void {
const self = @fieldParentPtr(Fat32Self, "instance", fs.instance);
// As close can't error, if provided with a invalid Node that isn't opened or try to close
// the same file twice, will just do nothing.
if (self.opened_files.remove(node)) |entry_node| {
self.allocator.destroy(entry_node.value);
self.allocator.destroy(node);
}
}
/// See vfs.FileSystem.read
fn read(fs: *const vfs.FileSystem, node: *const vfs.FileNode, buffer: []u8) (Allocator.Error || vfs.Error)!usize {
const self = @fieldParentPtr(Fat32Self, "instance", fs.instance);
const cast_node = @ptrCast(*const vfs.Node, node);
const opened_node = self.opened_files.get(cast_node) orelse return vfs.Error.NotOpened;
const size = std.math.min(buffer.len, opened_node.size);
var it = ClusterChainIterator.init(self.allocator, self.fat_config, opened_node.cluster, self.stream) catch |e| switch (e) {
error.OutOfMemory => return error.OutOfMemory,
else => {
log.err("Error initialising the cluster chain iterator. Error: {}\n", .{e});
return vfs.Error.Unexpected;
},
};
defer it.deinit();
var index: usize = 0;
while (it.read(buffer[index..size]) catch |e| {
log.err("Error reading the cluster chain iterator. Error: {}\n", .{e});
return vfs.Error.Unexpected;
}) |next_index| {
index += next_index;
}
return size;
}
/// See vfs.FileSystem.write
fn write(fs: *const vfs.FileSystem, node: *const vfs.FileNode, bytes: []const u8) (Allocator.Error || vfs.Error)!usize {
const self = @fieldParentPtr(Fat32Self, "instance", fs.instance);
const cast_node = @ptrCast(*const vfs.Node, node);
const opened_node = self.opened_files.get(cast_node) orelse return vfs.Error.NotOpened;
// TODO: Future PR
return 0;
}
/// See vfs.FileSystem.open
fn open(fs: *const vfs.FileSystem, dir: *const vfs.DirNode, name: []const u8, flags: vfs.OpenFlags, open_args: vfs.OpenArgs) (Allocator.Error || vfs.Error)!*vfs.Node {
return switch (flags) {
.NO_CREATION => openImpl(fs, dir, name),
.CREATE_DIR, .CREATE_FILE, .CREATE_SYMLINK => createFileOrDirOrSymlink(fs, dir, name, flags, open_args),
};
}
///
/// Helper function for creating the correct *Node
///
/// Arguments:
/// IN self: *Fat32Self - Self, needed for the allocator and underlying filesystem
/// IN cluster: u32 - When creating a file, the cluster the file is at.
/// IN size: u32 - When creating a file, the size of the file is at.
/// IN flags: vfs.OpenFlags - The open flags for deciding on the Node type.
/// IN open_args: vfs.OpenArgs - The open arguments when creating a symlink.
///
/// Return: *vfs.Node
/// The VFS Node
///
/// Error: Allocator.Error || vfs.Error
/// Allocator.Error - Not enough memory for allocating the Node
/// vfs.Error.NoSymlinkTarget - See vfs.Error.NoSymlinkTarget.
///
fn createNode(self: *Fat32Self, cluster: u32, size: u32, flags: vfs.OpenFlags, open_args: vfs.OpenArgs) (Allocator.Error || vfs.Error)!*vfs.Node {
var node = try self.allocator.create(vfs.Node);
errdefer self.allocator.destroy(node);
node.* = switch (flags) {
.CREATE_DIR => .{ .Dir = .{ .fs = self.fs, .mount = null } },
.CREATE_FILE => .{ .File = .{ .fs = self.fs } },
.CREATE_SYMLINK => if (open_args.symlink_target) |target|
.{ .Symlink = target }
else
return vfs.Error.NoSymlinkTarget,
.NO_CREATION => unreachable,
};
// Create the opened info struct
var opened_info = try self.allocator.create(OpenedInfo);
errdefer self.allocator.destroy(opened_info);
opened_info.* = .{
.cluster = cluster,
.size = size,
};
try self.opened_files.put(node, opened_info);
return node;
}
///
/// A helper function for getting the cluster from an opened directory node.
///
/// Arguments:
/// IN self: *const Fat32Self - Self, used to get the opened nodes.
/// IN dir: *const vfs.DirNode - The directory node to get the cluster for.
///
/// Return: u32
/// The cluster number for the directory node.
///
/// Error: vfs.Error
/// error.NotOpened - If directory node isn't opened.
///
fn getDirCluster(self: *Fat32Self, dir: *const vfs.DirNode) vfs.Error!u32 {
return if (std.meta.eql(dir, self.fs.getRootNode(self.fs))) self.root_node.cluster else brk: {
const parent = self.opened_files.get(@ptrCast(*const vfs.Node, dir)) orelse return vfs.Error.NotOpened;
// Cluster 0 means is the root directory cluster
break :brk if (parent.cluster == 0) self.fat_config.root_directory_cluster else parent.cluster;
};
}
///
/// The helper function for opening a file/folder, no creation.
///
/// Arguments:
/// IN fs: *const vfs.FileSystem - The underlying filesystem.
/// IN dir: *const vfs.DirNode - The parent directory.
/// IN name: []const u8 - The name of the file/folder to open.
///
/// Return: *vfs.Node
/// The VFS Node for the opened file/folder.
///
/// Error: Allocator.Error || vfs.Error
/// Allocator.Error - Not enough memory for allocating memory
/// vfs.Error.NoSuchFileOrDir - Error if the file/folder doesn't exist.
/// vfs.Error.Unexpected - An error occurred whilst reading the file system, this
/// can be caused by a parsing error or errors on reading
/// or seeking the underlying stream. If this occurs, then
/// the real error is printed using `log.err`.
///
fn openImpl(fs: *const vfs.FileSystem, dir: *const vfs.DirNode, name: []const u8) (Allocator.Error || vfs.Error)!*vfs.Node {
const self = @fieldParentPtr(Fat32Self, "instance", fs.instance);
// TODO: Cache the files in this dir, so when opening, don't have to iterator the directory every time
// Iterate over the directory and find the file/folder
const cluster = try self.getDirCluster(dir);
var it = EntryIterator.init(self.allocator, self.fat_config, cluster, self.stream) catch |e| switch (e) {
error.OutOfMemory => return error.OutOfMemory,
else => {
log.err("Error initialising the entry iterator. Error: {}\n", .{e});
return vfs.Error.Unexpected;
},
};
defer it.deinit();
while (it.next() catch |e| switch (e) {
error.OutOfMemory => return error.OutOfMemory,
else => {
log.err("Error initialising the entry iterator. Error: {}\n", .{e});
return vfs.Error.Unexpected;
},
}) |entry| {
defer entry.deinit();
// File name compare is case insensitive
var match: bool = brk: {
if (entry.long_name) |long_name| {
if (std.ascii.eqlIgnoreCase(name, long_name)) {
break :brk true;
}
}
var short_buff: [33]u8 = undefined;
const s_end = entry.short_name.getName(short_buff[0..]);
if (std.ascii.eqlIgnoreCase(name, short_buff[0..s_end])) {
break :brk true;
}
break :brk false;
};
if (match) {
const open_type = if (entry.short_name.isDir()) vfs.OpenFlags.CREATE_DIR else vfs.OpenFlags.CREATE_FILE;
return self.createNode(entry.short_name.getCluster(), entry.short_name.size, open_type, .{});
}
}
return vfs.Error.NoSuchFileOrDir;
}
///
/// Helper function for creating a file, folder or symlink.
///
/// Arguments:
/// IN fs: *const vfs.FileSystem - The underlying filesystem.
/// IN dir: *const vfs.DirNode - The parent directory.
/// IN name: []const u8 - The name of the file/folder to open.
/// IN flags: vfs.OpenFlags - The open flags for if creating a file/folder/symlink.
/// IN open_args: vfs.OpenArgs - The open arguments if creating a symlink.
///
/// Return: *vfs.Node
/// The VFS Node for the opened/created file/folder.
///
/// Error: Allocator.Error || ReadError || SeekError || vfs.Error
/// Allocator.Error - Not enough memory for allocating memory
/// vfs.Error.NoSuchFileOrDir - Error if creating a symlink and no target is provided.
///
fn createFileOrDirOrSymlink(fs: *const vfs.FileSystem, dir: *const vfs.DirNode, name: []const u8, flags: vfs.OpenFlags, open_args: vfs.OpenArgs) (Allocator.Error || vfs.Error)!*vfs.Node {
const self = @fieldParentPtr(Fat32Self, "instance", fs.instance);
// TODO: Future PR
return vfs.Error.NoSuchFileOrDir;
}
///
/// Deinitialise this file system. This frees the root node, virtual filesystem and self.
/// This asserts that there are no open files left.
///
/// Arguments:
/// IN self: *Fat32Self - Self to free.
///
pub fn destroy(self: *Fat32Self) void {
// Make sure we have closed all files
// TODO: Should this deinit close any open files instead?
std.debug.assert(self.opened_files.count() == 0);
self.opened_files.deinit();
self.allocator.destroy(self.root_node.node);
self.allocator.destroy(self.fs);
self.allocator.destroy(self);
}
///
/// Initialise a FAT32 filesystem.
///
/// Arguments:
/// IN allocator: *Allocator - Allocate memory.
/// IN stream: StreamType - The underlying stream that the filesystem will sit on.
///
/// Return: *Fat32
/// The pointer to a FAT32 filesystem.
///
/// Error: Allocator.Error || ReadError || SeekError || Fat32Self.Error
/// Allocator.Error - If there is no more memory. Any memory allocated will be freed.
/// ReadError - If there is an error reading from the stream.
/// SeekError - If there si an error seeking the stream.
/// Fat32Self.Error - If there is an error when parsing the stream to set up a fAT32
/// filesystem. See Error for the list of possible errors.
///
pub fn create(allocator: *Allocator, stream: StreamType) (Allocator.Error || ReadError || SeekError || Fat32Self.Error)!*Fat32Self {
log.debug("Init\n", .{});
defer log.debug("Done\n", .{});
// We need to get the root directory sector. For this we need to read the boot sector.
var boot_sector_raw = try allocator.alloc(u8, 512);
defer allocator.free(boot_sector_raw);
const seek_stream = stream.seekableStream();
const read_stream = stream.reader();
// Ensure we are the beginning
try seek_stream.seekTo(0);
const read_count = try read_stream.readAll(boot_sector_raw[0..]);
if (read_count != 512) {
return Error.BadRead;
}
// Check the boot signature
if (boot_sector_raw[510] != 0x55 or boot_sector_raw[511] != 0xAA) {
return Error.BadMBRMagic;
}
// Parse the boot sector to extract the relevant information
const boot_sector = initStruct(BootRecord, boot_sector_raw[0..90]);
// Make sure the root cluster isn't 0 or 1
if (boot_sector.root_directory_cluster < 2) {
return Error.BadRootCluster;
}
// Make sure we have 2 FATs as this is common for FAT32, and we are going to not accept more or less FATs
if (boot_sector.fat_count != 2) {
return Error.BadFATCount;
}
// Make sure the FATs are mirrored (flags = 0x0)
if (boot_sector.mirror_flags != 0x00) {
return Error.NotMirror;
}
// Only accept fixed disks, no floppies
if (boot_sector.media_descriptor_type != 0xF8) {
return Error.BadMedia;
}
// Make sure the parts there were used for FAT12/16 are zero
if (boot_sector.root_directory_size != 0 or boot_sector.sectors_per_fat_12_16 != 0 or boot_sector.total_sectors_12_16 != 0) {
return Error.BadFat32;
}
// Check the signature
if (boot_sector.signature != 0x29) {
return Error.BadSignature;
}
// Check the filesystem type
if (!std.mem.eql(u8, "FAT32 ", boot_sector.filesystem_type[0..])) {
return Error.BadFSType;
}
// Read the FSInfo block
// 0xFFFFFFFF is for unknown sizes
var number_free_clusters: u32 = 0xFFFFFFFF;
var next_free_cluster: u32 = 0xFFFFFFFF;
var has_fs_info = false;
if (boot_sector.fsinfo_sector != 0x0000 and boot_sector.fsinfo_sector != 0xFFFF) {
var fs_info_raw = try allocator.alloc(u8, 512);
defer allocator.free(fs_info_raw);
try seek_stream.seekTo(boot_sector.fsinfo_sector * boot_sector.bytes_per_sector);
const fs_read_count = try read_stream.readAll(fs_info_raw[0..]);
if (fs_read_count != 512) {
return Error.BadRead;
}
const fs_info = initStruct(FSInfo, fs_info_raw[0..]);
// Check the signatures
if (fs_info.lead_signature == 0x41615252 and fs_info.struct_signature == 0x61417272 and fs_info.tail_signature == 0xAA550000) {
// It should be range checked at least to make sure it is <= volume cluster count.
const usable_sectors = boot_sector.total_sectors - boot_sector.reserved_sectors - (boot_sector.fat_count * boot_sector.sectors_per_fat);
const usable_clusters = @divFloor(usable_sectors, boot_sector.sectors_per_cluster) - 1;
if (usable_clusters >= fs_info.number_free_clusters) {
number_free_clusters = fs_info.number_free_clusters;
}
next_free_cluster = fs_info.next_free_cluster;
has_fs_info = true;
}
}
// Figure out the end marker used in the cluster chain by reading the FAT
// FAT is just after the reserved sectors +4 as it is the second entry of u32 entries
try seek_stream.seekTo(boot_sector.reserved_sectors * boot_sector.bytes_per_sector + 4);
var end_marker_raw: [4]u8 = undefined;
const fat_read_count = try read_stream.readAll(end_marker_raw[0..]);
if (fat_read_count != 4) {
return Error.BadRead;
}
const cluster_end_marker = std.mem.bytesAsSlice(u32, end_marker_raw[0..])[0] & 0x0FFFFFFF;
// Have performed the checks, create the filesystem
const fs = try allocator.create(vfs.FileSystem);
errdefer allocator.destroy(fs);
const root_node = try allocator.create(vfs.Node);
errdefer allocator.destroy(root_node);
const fat32_fs = try allocator.create(Fat32Self);
errdefer allocator.destroy(fat32_fs);
// Record the relevant information
const fat_config = FATConfig{
.bytes_per_sector = boot_sector.bytes_per_sector,
.sectors_per_cluster = boot_sector.sectors_per_cluster,
.reserved_sectors = boot_sector.reserved_sectors,
.hidden_sectors = boot_sector.hidden_sectors,
.total_sectors = boot_sector.total_sectors,
.sectors_per_fat = boot_sector.sectors_per_fat,
.root_directory_cluster = boot_sector.root_directory_cluster,
.fsinfo_sector = boot_sector.fsinfo_sector,
.backup_boot_sector = boot_sector.backup_boot_sector,
.has_fs_info = has_fs_info,
.number_free_clusters = number_free_clusters,
.next_free_cluster = next_free_cluster,
.cluster_end_marker = cluster_end_marker,
};
fat32_fs.* = .{
.fs = fs,
.allocator = allocator,
.instance = 32,
.root_node = .{
.node = root_node,
.cluster = fat_config.root_directory_cluster,
},
.fat_config = fat_config,
.stream = stream,
.opened_files = AutoHashMap(*const vfs.Node, *OpenedInfo).init(allocator),
};
root_node.* = .{
.Dir = .{
.fs = fs,
.mount = null,
},
};
fs.* = .{
.open = open,
.close = close,
.read = read,
.write = write,
.instance = &fat32_fs.instance,
.getRootNode = getRootNode,
};
return fat32_fs;
}
};
}
///
/// Initialise a FAT32 filesystem. This will take a stream that conforms to the reader(), writer(),
/// and seekableStream() interfaces. For example, FixedBufferStream or File. A pointer to this is
/// allowed. This will allow the use of different devices such as Hard drives or RAM disks to have
/// a FAT32 filesystem abstraction. We assume the FAT32 will be the only filesystem present and no
/// partition schemes are present. So will expect a valid boot sector.
///
/// Arguments:
/// IN allocator: *Allocator - An allocator.
/// IN stream: anytype - A stream this is used to read and seek a raw disk or memory to
/// be parsed as a FAT32 filesystem. E.g. a FixedBufferStream.
///
/// Return: *Fat32FS(@TypeOf(stream))
/// A pointer to a FAT32 filesystem.
///
/// Error: Allocator.Error || Fat32FS(@TypeOf(stream)).Error
/// Allocator.Error - If there isn't enough memory to create the filesystem.
///
pub fn initialiseFAT32(allocator: *Allocator, stream: anytype) (Allocator.Error || ErrorSet(@TypeOf(stream)) || Fat32FS(@TypeOf(stream)).Error)!*Fat32FS(@TypeOf(stream)) {
return Fat32FS(@TypeOf(stream)).create(allocator, stream);
}
///
/// Read the test files and write them to the test FAT32 filesystem.
///
/// Arguments:
/// IN comptime StreamType: type - The stream type.
/// IN fat32fs: *Fat32FS(StreamType) - The test filesystem.
///
/// Error: Allocator.Error || ErrorSet(StreamType) || vfs.Error || std.fs.File.OpenError || std.fs.File.ReadError
/// Allocator.Error - Error when allocating memory for reading the file content.
/// ErrorSet(StreamType) - Error when writing to the test filesystem.
/// vfs.Error - Error with VFS operations.
/// std.fs.File.OpenError - Error when opening the test files.
/// std.fs.File.ReadError - Error when reading the test files.
///
fn testWriteTestFiles(comptime StreamType: type, fat32fs: *Fat32FS(StreamType)) (Allocator.Error || ErrorSet(StreamType) || vfs.Error || std.fs.File.OpenError || std.fs.File.ReadError)!void {
vfs.setRoot(fat32fs.root_node.node);
var test_files = try std.fs.cwd().openDir("test/fat32/test_files", .{ .iterate = true });
defer test_files.close();
var it = test_files.iterate();
while (try it.next()) |file| {
// Open the test file
const test_file = try test_files.openFile(file.name, .{});
defer test_file.close();
// Read the content
const test_file_content = try test_file.readToEndAlloc(std.testing.allocator, 0xFFFF);
defer std.testing.allocator.free(test_file_content);
// TODO: Once the write PR is complete, then write the files
// // Open the test file to the FAT32 filesystem
// const f1 = try vfs.openFile(file, .CREATE_FILE);
// defer f1.close();
// // Write the content
// const written = try f1.write(test_file_content);
// if (written != test_file_content.len) {
// @panic("Written not the same for content length");
// }
}
}
test "LongName.getName" {
{
const lfn = LongName{
.order = 0x00,
.first = [5]u16{ '1', '2', '3', '4', '5' },
.check_sum = 0x00,
.second = [6]u16{ '1', '2', '3', 0x0000, 0xFFFF, 0xFFFF },
.third = [2]u16{ 0xFFFF, 0xFFFF },
};
// 2 * 13 u16's
var buff: [26]u8 = undefined;
const end = try lfn.getName(buff[0..]);
expectEqualSlices(u8, "12345123", buff[0..end]);
}
{
const lfn = LongName{
.order = 0x00,
.first = [5]u16{ 0x20AC, '1', 0x20AC, '2', 0x0000 },
.check_sum = 0x00,
.second = [6]u16{ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF },
.third = [2]u16{ 0xFFFF, 0xFFFF },
};
// 2 * 13 u16's
var buff: [26]u8 = undefined;
const end = try lfn.getName(buff[0..]);
expectEqualSlices(u8, "€1€2", buff[0..end]);
}
{
const lfn = LongName{
.order = 0x00,
.first = [5]u16{ '1', '1', '1', '1', '1' },
.check_sum = 0x00,
.second = [6]u16{ '1', '1', '1', '1', '1', '1' },
.third = [2]u16{ '1', 0xD801 },
};
// 2 * 13 u16's
var buff: [26]u8 = undefined;
expectError(error.DanglingSurrogateHalf, lfn.getName(buff[0..]));
}
{
const lfn = LongName{
.order = 0x00,
.first = [5]u16{ 0xD987, '1', 0xFFFF, 0xFFFF, 0xFFFF },
.check_sum = 0x00,
.second = [6]u16{ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF },
.third = [2]u16{ 0xFFFF, 0xFFFF },
};
// 2 * 13 u16's
var buff: [26]u8 = undefined;
expectError(error.ExpectedSecondSurrogateHalf, lfn.getName(buff[0..]));
}
{
const lfn = LongName{
.order = 0x00,
.first = [5]u16{ 0xDD87, '1', 0xFFFF, 0xFFFF, 0xFFFF },
.check_sum = 0x00,
.second = [6]u16{ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF },
.third = [2]u16{ 0xFFFF, 0xFFFF },
};
// 2 * 13 u16's
var buff: [26]u8 = undefined;
expectError(error.UnexpectedSecondSurrogateHalf, lfn.getName(buff[0..]));
}
}
test "ShortName.getName - File" {
{
const sfn = ShortName{
.name = "12345678".*,
.extension = "123".*,
.attributes = 0x00,
.time_created_tenth = 0x00,
.time_created = 0x0000,
.date_created = 0x0000,
.date_last_access = 0x0000,
.cluster_high = 0x0000,
.time_last_modification = 0x0000,
.date_last_modification = 0x0000,
.cluster_low = 0x0000,
.size = 0x00000000,
};
var name: [12]u8 = undefined;
const name_end = sfn.getName(name[0..]);
expectEqualSlices(u8, "12345678.123", name[0..name_end]);
}
{
const sfn = ShortName{
.name = "12345 ".*,
.extension = "123".*,
.attributes = 0x00,
.time_created_tenth = 0x00,
.time_created = 0x0000,
.date_created = 0x0000,
.date_last_access = 0x0000,
.cluster_high = 0x0000,
.time_last_modification = 0x0000,
.date_last_modification = 0x0000,
.cluster_low = 0x0000,
.size = 0x00000000,
};
var name: [12]u8 = undefined;
const name_end = sfn.getName(name[0..]);
expectEqualSlices(u8, "12345.123", name[0..name_end]);
}
{
const sfn = ShortName{
.name = "12345 ".*,
.extension = "1 ".*,
.attributes = 0x00,
.time_created_tenth = 0x00,
.time_created = 0x0000,
.date_created = 0x0000,
.date_last_access = 0x0000,
.cluster_high = 0x0000,
.time_last_modification = 0x0000,
.date_last_modification = 0x0000,
.cluster_low = 0x0000,
.size = 0x00000000,
};
var name: [12]u8 = undefined;
const name_end = sfn.getName(name[0..]);
expectEqualSlices(u8, "12345.1", name[0..name_end]);
}
{
const sfn = ShortName{
.name = "\u{05}2345 ".*,
.extension = "1 ".*,
.attributes = 0x00,
.time_created_tenth = 0x00,
.time_created = 0x0000,
.date_created = 0x0000,
.date_last_access = 0x0000,
.cluster_high = 0x0000,
.time_last_modification = 0x0000,
.date_last_modification = 0x0000,
.cluster_low = 0x0000,
.size = 0x00000000,
};
var name: [12]u8 = undefined;
const name_end = sfn.getName(name[0..]);
expectEqualSlices(u8, "σ2345.1", name[0..name_end]);
}
{
const sfn = ShortName{
.name = [_]u8{ 0x90, 0xA0 } ++ "345 ".*,
.extension = "1 ".*,
.attributes = 0x00,
.time_created_tenth = 0x00,
.time_created = 0x0000,
.date_created = 0x0000,
.date_last_access = 0x0000,
.cluster_high = 0x0000,
.time_last_modification = 0x0000,
.date_last_modification = 0x0000,
.cluster_low = 0x0000,
.size = 0x00000000,
};
var name: [12]u8 = undefined;
const name_end = sfn.getName(name[0..]);
expectEqualSlices(u8, "Éá345.1", name[0..name_end]);
}
{
const sfn = ShortName{
.name = "12345 ".*,
.extension = "1".* ++ [_]u8{0xB0} ++ " ".*,
.attributes = 0x00,
.time_created_tenth = 0x00,
.time_created = 0x0000,
.date_created = 0x0000,
.date_last_access = 0x0000,
.cluster_high = 0x0000,
.time_last_modification = 0x0000,
.date_last_modification = 0x0000,
.cluster_low = 0x0000,
.size = 0x00000000,
};
var name: [12]u8 = undefined;
const name_end = sfn.getName(name[0..]);
expectEqualSlices(u8, "12345.1░", name[0..name_end]);
}
}
test "ShortName.getName - Dir" {
{
const sfn = ShortName{
.name = "12345678".*,
.extension = " ".*,
.attributes = @enumToInt(ShortName.Attributes.Directory),
.time_created_tenth = 0x00,
.time_created = 0x0000,
.date_created = 0x0000,
.date_last_access = 0x0000,
.cluster_high = 0x0000,
.time_last_modification = 0x0000,
.date_last_modification = 0x0000,
.cluster_low = 0x0000,
.size = 0x00000000,
};
var name: [12]u8 = undefined;
const name_end = sfn.getName(name[0..]);
expectEqualSlices(u8, "12345678", name[0..name_end]);
}
{
const sfn = ShortName{
.name = "12345 ".*,
.extension = " ".*,
.attributes = @enumToInt(ShortName.Attributes.Directory),
.time_created_tenth = 0x00,
.time_created = 0x0000,
.date_created = 0x0000,
.date_last_access = 0x0000,
.cluster_high = 0x0000,
.time_last_modification = 0x0000,
.date_last_modification = 0x0000,
.cluster_low = 0x0000,
.size = 0x00000000,
};
var name: [12]u8 = undefined;
const name_end = sfn.getName(name[0..]);
expectEqualSlices(u8, "12345", name[0..name_end]);
}
{
const sfn = ShortName{
.name = "\u{05}2345 ".*,
.extension = " ".*,
.attributes = @enumToInt(ShortName.Attributes.Directory),
.time_created_tenth = 0x00,
.time_created = 0x0000,
.date_created = 0x0000,
.date_last_access = 0x0000,
.cluster_high = 0x0000,
.time_last_modification = 0x0000,
.date_last_modification = 0x0000,
.cluster_low = 0x0000,
.size = 0x00000000,
};
var name: [12]u8 = undefined;
const name_end = sfn.getName(name[0..]);
expectEqualSlices(u8, "σ2345", name[0..name_end]);
}
{
const sfn = ShortName{
.name = "12345 ".*,
.extension = "123".*,
.attributes = @enumToInt(ShortName.Attributes.Directory),
.time_created_tenth = 0x00,
.time_created = 0x0000,
.date_created = 0x0000,
.date_last_access = 0x0000,
.cluster_high = 0x0000,
.time_last_modification = 0x0000,
.date_last_modification = 0x0000,
.cluster_low = 0x0000,
.size = 0x00000000,
};
var name: [12]u8 = undefined;
const name_end = sfn.getName(name[0..]);
expectEqualSlices(u8, "12345", name[0..name_end]);
}
}
test "ShortName.getSFNName" {
const sfn = ShortName{
.name = [_]u8{ 0x05, 0xAA } ++ "345 ".*,
.extension = "1 ".*,
.attributes = 0x00,
.time_created_tenth = 0x00,
.time_created = 0x0000,
.date_created = 0x0000,
.date_last_access = 0x0000,
.cluster_high = 0x0000,
.time_last_modification = 0x0000,
.date_last_modification = 0x0000,
.cluster_low = 0x0000,
.size = 0x00000000,
};
const name = sfn.getSFNName();
const expected = [_]u8{ 0x05, 0xAA } ++ "345 1 ";
expectEqualSlices(u8, expected, name[0..]);
}
test "ShortName.isDir" {
{
const sfn = ShortName{
.name = "12345678".*,
.extension = "123".*,
.attributes = @enumToInt(ShortName.Attributes.Directory),
.time_created_tenth = 0x00,
.time_created = 0x0000,
.date_created = 0x0000,
.date_last_access = 0x0000,
.cluster_high = 0x0000,
.time_last_modification = 0x0000,
.date_last_modification = 0x0000,
.cluster_low = 0x0000,
.size = 0x00000000,
};
expect(sfn.isDir());
}
{
const sfn = ShortName{
.name = "12345678".*,
.extension = "123".*,
.attributes = @enumToInt(ShortName.Attributes.None),
.time_created_tenth = 0x00,
.time_created = 0x0000,
.date_created = 0x0000,
.date_last_access = 0x0000,
.cluster_high = 0x0000,
.time_last_modification = 0x0000,
.date_last_modification = 0x0000,
.cluster_low = 0x0000,
.size = 0x00000000,
};
expect(!sfn.isDir());
}
}
test "ShortName.getCluster" {
const sfn = ShortName{
.name = "12345678".*,
.extension = "123".*,
.attributes = @enumToInt(ShortName.Attributes.None),
.time_created_tenth = 0x00,
.time_created = 0x0000,
.date_created = 0x0000,
.date_last_access = 0x0000,
.cluster_high = 0xABCD,
.time_last_modification = 0x0000,
.date_last_modification = 0x0000,
.cluster_low = 0x1234,
.size = 0x00000000,
};
expectEqual(sfn.getCluster(), 0xABCD1234);
}
test "ShortName.calcCheckSum" {
const sfn = ShortName{
.name = "12345678".*,
.extension = "123".*,
.attributes = @enumToInt(ShortName.Attributes.None),
.time_created_tenth = 0x00,
.time_created = 0x0000,
.date_created = 0x0000,
.date_last_access = 0x0000,
.cluster_high = 0xABCD,
.time_last_modification = 0x0000,
.date_last_modification = 0x0000,
.cluster_low = 0x1234,
.size = 0x00000000,
};
expectEqual(sfn.calcCheckSum(), 0x7A);
}
test "Fat32FS initialise test files" {
var test_file_buf = try std.testing.allocator.alloc(u8, 1024 * 1024);
defer std.testing.allocator.free(test_file_buf);
var stream = &std.io.fixedBufferStream(test_file_buf[0..]);
try mkfat32.Fat32.make(.{}, stream, true);
var test_fs = try initialiseFAT32(std.testing.allocator, stream);
defer test_fs.destroy();
// Write the test files
try testWriteTestFiles(@TypeOf(stream), test_fs);
// Open the known good image to compare to
const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{});
defer test_fat32_image.close();
// TODO: Loop over the test files and open then
// Compare the long and short entries
// The time stamps will be different so can't to complete byte compare
}
test "Fat32FS.getRootNode" {
const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{});
defer test_fat32_image.close();
var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image);
defer test_fs.destroy();
expectEqual(test_fs.fs.getRootNode(test_fs.fs), &test_fs.root_node.node.Dir);
expectEqual(test_fs.root_node.cluster, 2);
expectEqual(test_fs.fat_config.root_directory_cluster, 2);
}
test "Fat32FS.init no error" {
const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{});
defer test_fat32_image.close();
var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image);
defer test_fs.destroy();
}
test "Fat32FS.init errors" {
const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{});
defer test_fat32_image.close();
var test_file_buf = try std.testing.allocator.alloc(u8, (32 * 512 + 4) + 1);
defer std.testing.allocator.free(test_file_buf);
const read = try test_fat32_image.reader().readAll(test_file_buf[0..]);
const stream = &std.io.fixedBufferStream(test_file_buf[0..]);
// BadMBRMagic
test_file_buf[510] = 0x00;
expectError(error.BadMBRMagic, initialiseFAT32(std.testing.allocator, stream));
test_file_buf[510] = 0x55;
test_file_buf[511] = 0x00;
expectError(error.BadMBRMagic, initialiseFAT32(std.testing.allocator, stream));
test_file_buf[511] = 0xAA;
// BadRootCluster
// Little endian, so just eed to set the upper bytes
test_file_buf[44] = 0;
expectError(error.BadRootCluster, initialiseFAT32(std.testing.allocator, stream));
test_file_buf[44] = 1;
expectError(error.BadRootCluster, initialiseFAT32(std.testing.allocator, stream));
test_file_buf[44] = 2;
// BadFATCount
test_file_buf[16] = 0;
expectError(error.BadFATCount, initialiseFAT32(std.testing.allocator, stream));
test_file_buf[16] = 1;
expectError(error.BadFATCount, initialiseFAT32(std.testing.allocator, stream));
test_file_buf[16] = 10;
expectError(error.BadFATCount, initialiseFAT32(std.testing.allocator, stream));
test_file_buf[16] = 2;
// NotMirror
test_file_buf[40] = 1;
expectError(error.NotMirror, initialiseFAT32(std.testing.allocator, stream));
test_file_buf[40] = 10;
expectError(error.NotMirror, initialiseFAT32(std.testing.allocator, stream));
test_file_buf[40] = 0;
// BadMedia
test_file_buf[21] = 0xF0;
expectError(error.BadMedia, initialiseFAT32(std.testing.allocator, stream));
test_file_buf[21] = 0xF8;
// BadFat32
test_file_buf[17] = 10;
expectError(error.BadFat32, initialiseFAT32(std.testing.allocator, stream));
test_file_buf[17] = 0;
test_file_buf[19] = 10;
expectError(error.BadFat32, initialiseFAT32(std.testing.allocator, stream));
test_file_buf[19] = 0;
test_file_buf[22] = 10;
expectError(error.BadFat32, initialiseFAT32(std.testing.allocator, stream));
test_file_buf[22] = 0;
// BadSignature
test_file_buf[66] = 0x28;
expectError(error.BadSignature, initialiseFAT32(std.testing.allocator, stream));
test_file_buf[66] = 0x29;
// BadFSType
// Change from FAT32 to FAT16
test_file_buf[85] = '1';
test_file_buf[86] = '6';
expectError(error.BadFSType, initialiseFAT32(std.testing.allocator, stream));
test_file_buf[85] = '3';
test_file_buf[86] = '2';
// Test the bad reads
// Boot sector
expectError(error.BadRead, initialiseFAT32(std.testing.allocator, &std.io.fixedBufferStream(test_file_buf[0..510])));
// FSInfo (we have one)
expectError(error.BadRead, initialiseFAT32(std.testing.allocator, &std.io.fixedBufferStream(test_file_buf[0 .. 512 + 100])));
// FAT
expectError(error.BadRead, initialiseFAT32(std.testing.allocator, &std.io.fixedBufferStream(test_file_buf[0 .. (32 * 512 + 4) + 1])));
}
test "Fat32FS.init free memory" {
const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{});
defer test_fat32_image.close();
const allocations: usize = 5;
var i: usize = 0;
while (i < allocations) : (i += 1) {
var fa = std.testing.FailingAllocator.init(std.testing.allocator, i);
expectError(error.OutOfMemory, initialiseFAT32(&fa.allocator, test_fat32_image));
}
}
test "Fat32FS.init FATConfig expected" {
const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{});
defer test_fat32_image.close();
var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image);
defer test_fs.destroy();
// This is the expected FAT config from the initialised FAT device
const expected = FATConfig{
.bytes_per_sector = 512,
.sectors_per_cluster = 1,
.reserved_sectors = 32,
.hidden_sectors = 0,
.total_sectors = 66583,
.sectors_per_fat = 513,
.root_directory_cluster = 2,
.fsinfo_sector = 1,
.backup_boot_sector = 6,
.has_fs_info = true,
.number_free_clusters = 65491,
.next_free_cluster = 35,
.cluster_end_marker = 0x0FFFFFFF,
};
expectEqual(expected, test_fs.fat_config);
}
test "Fat32FS.init FATConfig mix FSInfo" {
const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{});
defer test_fat32_image.close();
var test_file_buf = try std.testing.allocator.alloc(u8, 1024 * 1024);
defer std.testing.allocator.free(test_file_buf);
const read = try test_fat32_image.reader().readAll(test_file_buf[0..]);
const stream = &std.io.fixedBufferStream(test_file_buf[0..]);
// No FSInfo
{
// Force no FSInfo
test_file_buf[48] = 0x00;
var test_fs = try initialiseFAT32(std.testing.allocator, stream);
defer test_fs.destroy();
// This is the default config that should be produced from mkfat32.Fat32
const expected = FATConfig{
.bytes_per_sector = 512,
.sectors_per_cluster = 1,
.reserved_sectors = 32,
.hidden_sectors = 0,
.total_sectors = 66583,
.sectors_per_fat = 513,
.root_directory_cluster = 2,
.fsinfo_sector = 0,
.backup_boot_sector = 6,
.has_fs_info = false,
.number_free_clusters = 0xFFFFFFFF,
.next_free_cluster = 0xFFFFFFFF,
.cluster_end_marker = 0x0FFFFFFF,
};
expectEqual(expected, test_fs.fat_config);
test_file_buf[48] = 0x01;
}
// Bad Signatures
{
// Corrupt a signature
test_file_buf[512] = 0xAA;
var test_fs = try initialiseFAT32(std.testing.allocator, stream);
defer test_fs.destroy();
// This is the default config that should be produced from mkfat32.Fat32
const expected = FATConfig{
.bytes_per_sector = 512,
.sectors_per_cluster = 1,
.reserved_sectors = 32,
.hidden_sectors = 0,
.total_sectors = 66583,
.sectors_per_fat = 513,
.root_directory_cluster = 2,
.fsinfo_sector = 1,
.backup_boot_sector = 6,
.has_fs_info = false,
.number_free_clusters = 0xFFFFFFFF,
.next_free_cluster = 0xFFFFFFFF,
.cluster_end_marker = 0x0FFFFFFF,
};
expectEqual(expected, test_fs.fat_config);
test_file_buf[512] = 0x52;
}
// Bad number_free_clusters
{
// Make is massive
test_file_buf[512 + 4 + 480 + 4] = 0xAA;
test_file_buf[512 + 4 + 480 + 5] = 0xBB;
test_file_buf[512 + 4 + 480 + 6] = 0xCC;
test_file_buf[512 + 4 + 480 + 7] = 0xDD;
var test_fs = try initialiseFAT32(std.testing.allocator, stream);
defer test_fs.destroy();
// This is the default config that should be produced from mkfat32.Fat32
const expected = FATConfig{
.bytes_per_sector = 512,
.sectors_per_cluster = 1,
.reserved_sectors = 32,
.hidden_sectors = 0,
.total_sectors = 66583,
.sectors_per_fat = 513,
.root_directory_cluster = 2,
.fsinfo_sector = 1,
.backup_boot_sector = 6,
.has_fs_info = true,
.number_free_clusters = 0xFFFFFFFF,
.next_free_cluster = 35,
.cluster_end_marker = 0x0FFFFFFF,
};
expectEqual(expected, test_fs.fat_config);
}
}
test "ClusterChainIterator.checkRead - Within cluster, within FAT" {
// The undefined values are not used in checkRead
const fat_config = FATConfig{
.bytes_per_sector = 16,
.sectors_per_cluster = 1,
.reserved_sectors = 0,
.hidden_sectors = undefined,
.total_sectors = undefined,
.sectors_per_fat = undefined,
.root_directory_cluster = undefined,
.fsinfo_sector = undefined,
.backup_boot_sector = undefined,
.has_fs_info = undefined,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = undefined,
};
var buff_stream = [_]u8{};
var stream = &std.io.fixedBufferStream(buff_stream[0..]);
// First 2 are for other purposed and not needed, the third is the first real FAT entry
var fat = [_]u32{ 0x0FFFFFFF, 0xFFFFFFF8, 0xFFFFFFFF, 0x00000000 };
var it = Fat32FS(@TypeOf(stream)).ClusterChainIterator{
.allocator = undefined,
.cluster = 2,
.fat_config = fat_config,
.stream = stream,
.fat = fat[0..],
.table_offset = 0,
.cluster_offset = 0,
};
try it.checkRead();
// Nothing changed
expectEqual(it.cluster, 2);
expectEqual(it.cluster_offset, 0);
expectEqual(it.table_offset, 0);
expectEqualSlices(u32, it.fat, fat[0..]);
}
test "ClusterChainIterator.checkRead - Multiple clusters, within FAT" {
// The undefined values are not used in checkRead
// The value won't be valid FAT32 values, but this doesn't matter in this context so can make the stream buffer smaller
const fat_config = FATConfig{
.bytes_per_sector = 16,
.sectors_per_cluster = 1,
.reserved_sectors = 0,
.hidden_sectors = undefined,
.total_sectors = undefined,
.sectors_per_fat = undefined,
.root_directory_cluster = undefined,
.fsinfo_sector = undefined,
.backup_boot_sector = undefined,
.has_fs_info = undefined,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = undefined,
};
var buff_stream = [_]u8{};
var stream = &std.io.fixedBufferStream(buff_stream[0..]);
// First 2 are for other purposed and not needed, the third is the first real FAT entry
var fat = [_]u32{ 0x0FFFFFFF, 0xFFFFFFF8, 0x00000003, 0xFFFFFFFF };
var it = Fat32FS(@TypeOf(stream)).ClusterChainIterator{
.allocator = undefined,
.cluster = 2,
.fat_config = fat_config,
.stream = stream,
.fat = fat[0..],
.table_offset = 0,
.cluster_offset = 16,
};
// This will update the next cluster to read from
try it.checkRead();
// Updated the cluster only
expectEqual(it.cluster, 3);
expectEqual(it.cluster_offset, 0);
expectEqual(it.table_offset, 0);
expectEqualSlices(u32, it.fat, fat[0..]);
}
test "ClusterChainIterator.checkRead - Multiple clusters, outside FAT" {
// The undefined values are not used in checkRead
// The value won't be valid FAT32 values, but this doesn't matter in this context so can make the stream buffer smaller
const fat_config = FATConfig{
.bytes_per_sector = 16,
.sectors_per_cluster = 1,
.reserved_sectors = 0,
.hidden_sectors = undefined,
.total_sectors = undefined,
.sectors_per_fat = 1,
.root_directory_cluster = undefined,
.fsinfo_sector = undefined,
.backup_boot_sector = undefined,
.has_fs_info = undefined,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = 0x0FFFFFFF,
};
// Set the stream to all FF which represents the end of a FAT chain
var buff_stream = [_]u8{
// First 4 FAT (little endian)
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0x04, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF,
// Second 4 FAT. This is where it will seek to
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
};
var stream = &std.io.fixedBufferStream(buff_stream[0..]);
// First 2 are for other purposed and not needed, the third is the first real FAT entry
var fat = [_]u32{ 0x0FFFFFFF, 0xFFFFFFF8, 0x00000004, 0xFFFFFFFF };
var expected_fat = [_]u32{ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
var it = Fat32FS(@TypeOf(stream)).ClusterChainIterator{
.allocator = undefined,
.cluster = 2,
.fat_config = fat_config,
.stream = stream,
.fat = fat[0..],
.table_offset = 0,
.cluster_offset = 16,
};
// This will read the next cluster and read a new FAT cache
try it.checkRead();
// Updated the cluster and table offset
expectEqual(it.cluster, 4);
expectEqual(it.cluster_offset, 0);
expectEqual(it.table_offset, 1);
expectEqualSlices(u32, it.fat, expected_fat[0..]);
}
test "ClusterChainIterator.read - end of buffer" {
var stream = &std.io.fixedBufferStream(&[_]u8{});
var it = Fat32FS(@TypeOf(stream)).ClusterChainIterator{
.allocator = undefined,
.cluster = undefined,
.fat_config = undefined,
.stream = undefined,
.fat = undefined,
.table_offset = undefined,
.cluster_offset = undefined,
};
const actual = try it.read(&[_]u8{});
expectEqual(actual, null);
}
test "ClusterChainIterator.read - cluster 0" {
var stream = &std.io.fixedBufferStream(&[_]u8{});
var it = Fat32FS(@TypeOf(stream)).ClusterChainIterator{
.allocator = undefined,
.cluster = 0,
.fat_config = undefined,
.stream = undefined,
.fat = undefined,
.table_offset = undefined,
.cluster_offset = undefined,
};
var buff: [128]u8 = undefined;
const actual = try it.read(buff[0..]);
expectEqual(actual, null);
}
test "ClusterChainIterator.read - end of cluster chain" {
// The undefined values are not used in read
const end_cluster: u32 = 0x0FFFFFFF;
const fat_config = FATConfig{
.bytes_per_sector = undefined,
.sectors_per_cluster = undefined,
.reserved_sectors = undefined,
.hidden_sectors = undefined,
.total_sectors = undefined,
.sectors_per_fat = undefined,
.root_directory_cluster = undefined,
.fsinfo_sector = undefined,
.backup_boot_sector = undefined,
.has_fs_info = undefined,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = end_cluster,
};
var stream = &std.io.fixedBufferStream(&[_]u8{});
var it = Fat32FS(@TypeOf(stream)).ClusterChainIterator{
.allocator = undefined,
.cluster = end_cluster,
.fat_config = fat_config,
.stream = undefined,
.fat = undefined,
.table_offset = undefined,
.cluster_offset = undefined,
};
var buff: [128]u8 = undefined;
const actual = try it.read(buff[0..]);
expectEqual(actual, null);
}
test "ClusterChainIterator.read - BadRead" {
// The undefined values are not used in read
const fat_config = FATConfig{
.bytes_per_sector = 512,
.sectors_per_cluster = 1,
.reserved_sectors = 0,
.hidden_sectors = undefined,
.total_sectors = undefined,
.sectors_per_fat = 1,
.root_directory_cluster = undefined,
.fsinfo_sector = undefined,
.backup_boot_sector = undefined,
.has_fs_info = undefined,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = 0x0FFFFFFF,
};
var stream_buff: [1024]u8 = undefined;
var stream = &std.io.fixedBufferStream(stream_buff[0..]);
var it = Fat32FS(@TypeOf(stream)).ClusterChainIterator{
.allocator = undefined,
.cluster = 2,
.fat_config = fat_config,
.stream = stream,
.fat = undefined,
.table_offset = 0,
.cluster_offset = 0,
};
// Buffer is too small
var buff: [128]u8 = undefined;
expectError(error.BadRead, it.read(buff[0..]));
}
test "ClusterChainIterator.read - success" {
// The undefined values are not used in checkRead
// The value won't be valid FAT32 values, but this doesn't matter in this context so can make the stream buffer smaller
const fat_config = FATConfig{
.bytes_per_sector = 16,
.sectors_per_cluster = 1,
.reserved_sectors = 0,
.hidden_sectors = undefined,
.total_sectors = undefined,
.sectors_per_fat = 1,
.root_directory_cluster = undefined,
.fsinfo_sector = undefined,
.backup_boot_sector = undefined,
.has_fs_info = undefined,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = 0x0FFFFFFF,
};
var buff_stream = [_]u8{
// FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
// Backup FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
// Data region
'a', 'b', 'c', 'd',
'1', '2', '3', '4',
'A', 'B', 'C', 'D',
'!', '"', '$', '%',
};
var stream = &std.io.fixedBufferStream(buff_stream[0..]);
// First 2 are for other purposed and not needed, the third is the first real FAT entry
var fat = [_]u32{ 0x0FFFFFFF, 0xFFFFFFF8, 0xFFFFFFFF, 0xFFFFFFFF };
var expected_fat = [_]u32{ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
var it = Fat32FS(@TypeOf(stream)).ClusterChainIterator{
.allocator = undefined,
.cluster = 2,
.fat_config = fat_config,
.stream = stream,
.fat = fat[0..],
.table_offset = 0,
.cluster_offset = 0,
};
var buff: [16]u8 = undefined;
const read = try it.read(buff[0..]);
expectEqual(read, 16);
expectEqualSlices(u8, buff[0..], "abcd1234ABCD!\"$%");
expectEqual(it.table_offset, 0);
expectEqual(it.cluster_offset, 0);
expectEqual(it.cluster, 0xFFFFFFFF);
}
test "ClusterChainIterator.init - free on BadRead" {
const fat_config = FATConfig{
.bytes_per_sector = 16,
.sectors_per_cluster = 1,
.reserved_sectors = 0,
.hidden_sectors = undefined,
.total_sectors = undefined,
.sectors_per_fat = undefined,
.root_directory_cluster = undefined,
.fsinfo_sector = undefined,
.backup_boot_sector = undefined,
.has_fs_info = undefined,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = 0x0FFFFFFF,
};
var stream = &std.io.fixedBufferStream(&[_]u8{});
expectError(error.BadRead, Fat32FS(@TypeOf(stream)).ClusterChainIterator.init(std.testing.allocator, fat_config, 2, stream));
}
test "ClusterChainIterator.init - free on OutOfMemory" {
const fat_config = FATConfig{
.bytes_per_sector = 16,
.sectors_per_cluster = 1,
.reserved_sectors = 0,
.hidden_sectors = undefined,
.total_sectors = undefined,
.sectors_per_fat = 1,
.root_directory_cluster = undefined,
.fsinfo_sector = undefined,
.backup_boot_sector = undefined,
.has_fs_info = undefined,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = 0x0FFFFFFF,
};
var buff_stream = [_]u8{
// FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
// Backup FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
// Data region
'a', 'b', 'c', 'd',
'1', '2', '3', '4',
'A', 'B', 'C', 'D',
'!', '"', '$', '%',
};
var stream = &std.io.fixedBufferStream(buff_stream[0..]);
const allocations: usize = 1;
var i: usize = 0;
while (i < allocations) : (i += 1) {
var fa = std.testing.FailingAllocator.init(std.testing.allocator, i);
expectError(error.OutOfMemory, Fat32FS(@TypeOf(stream)).ClusterChainIterator.init(&fa.allocator, fat_config, 2, stream));
}
}
test "ClusterChainIterator.init - success and good read" {
const fat_config = FATConfig{
.bytes_per_sector = 16,
.sectors_per_cluster = 1,
.reserved_sectors = 0,
.hidden_sectors = undefined,
.total_sectors = undefined,
.sectors_per_fat = 1,
.root_directory_cluster = undefined,
.fsinfo_sector = undefined,
.backup_boot_sector = undefined,
.has_fs_info = undefined,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = 0x0FFFFFFF,
};
var buff_stream = [_]u8{
// FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
// Backup FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
// Data region
'a', 'b', 'c', 'd',
'1', '2', '3', '4',
'A', 'B', 'C', 'D',
'!', '"', '$', '%',
};
var stream = &std.io.fixedBufferStream(buff_stream[0..]);
var it = try Fat32FS(@TypeOf(stream)).ClusterChainIterator.init(std.testing.allocator, fat_config, 2, stream);
defer it.deinit();
var buff: [16]u8 = undefined;
// If orelse, then 'expectEqual(read, 16);' will fail
const read = (try it.read(buff[0..])) orelse 0;
expectEqual(read, 16);
expectEqualSlices(u8, buff[0..], "abcd1234ABCD!\"$%");
expectEqual(it.table_offset, 0);
expectEqual(it.cluster_offset, 0);
expectEqual(it.cluster, 0xFFFFFFFF);
const expect_null = try it.read(buff[read..]);
expectEqual(expect_null, null);
}
test "EntryIterator.checkRead - inside cluster block" {
const fat_config = FATConfig{
.bytes_per_sector = 16,
.sectors_per_cluster = 1,
.reserved_sectors = 0,
.hidden_sectors = undefined,
.total_sectors = undefined,
.sectors_per_fat = 1,
.root_directory_cluster = undefined,
.fsinfo_sector = undefined,
.backup_boot_sector = undefined,
.has_fs_info = undefined,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = 0x0FFFFFFF,
};
var buff_stream = [_]u8{
// FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0x04, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF,
// Backup FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
// Data region cluster 1
'a', 'b', 'c', 'd',
'1', '2', '3', '4',
'A', 'B', 'C', 'D',
'!', '"', '$', '%',
// Data region cluster 2
'e', 'f', 'g', 'h',
'5', '6', '7', '8',
'E', 'F', 'G', 'H',
'^', '&', '*', '(',
};
var stream = &std.io.fixedBufferStream(buff_stream[0..]);
var cluster_chain = try Fat32FS(@TypeOf(stream)).ClusterChainIterator.init(std.testing.allocator, fat_config, 2, stream);
defer cluster_chain.deinit();
var buff: [16]u8 = undefined;
std.mem.copy(u8, buff[0..], buff_stream[32..48]);
var it = Fat32FS(@TypeOf(cluster_chain.stream)).EntryIterator{
.allocator = undefined,
.cluster_block = buff[0..],
.index = 0,
.cluster_chain = cluster_chain,
};
expectEqualSlices(u8, it.cluster_block, "abcd1234ABCD!\"$%");
try it.checkRead();
// nothing changed
expectEqualSlices(u8, it.cluster_block, "abcd1234ABCD!\"$%");
}
test "EntryIterator.checkRead - read new cluster" {
const fat_config = FATConfig{
.bytes_per_sector = 16,
.sectors_per_cluster = 1,
.reserved_sectors = 0,
.hidden_sectors = undefined,
.total_sectors = undefined,
.sectors_per_fat = 1,
.root_directory_cluster = undefined,
.fsinfo_sector = undefined,
.backup_boot_sector = undefined,
.has_fs_info = undefined,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = 0x0FFFFFFF,
};
var buff_stream = [_]u8{
// FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF,
// Backup FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF,
// Data region cluster 1
'a', 'b', 'c', 'd',
'1', '2', '3', '4',
'A', 'B', 'C', 'D',
'!', '"', '$', '%',
// Data region cluster 2
'e', 'f', 'g', 'h',
'5', '6', '7', '8',
'E', 'F', 'G', 'H',
'^', '&', '*', '(',
};
var stream = &std.io.fixedBufferStream(buff_stream[0..]);
var cluster_chain = try Fat32FS(@TypeOf(stream)).ClusterChainIterator.init(std.testing.allocator, fat_config, 2, stream);
defer cluster_chain.deinit();
var buff: [16]u8 = undefined;
_ = try cluster_chain.read(buff[0..]);
var it = Fat32FS(@TypeOf(cluster_chain.stream)).EntryIterator{
.allocator = undefined,
.cluster_block = buff[0..],
.index = 16,
.cluster_chain = cluster_chain,
};
expectEqualSlices(u8, it.cluster_block, "abcd1234ABCD!\"$%");
try it.checkRead();
expectEqualSlices(u8, it.cluster_block, "efgh5678EFGH^&*(");
expectEqual(it.index, 0);
}
test "EntryIterator.checkRead - end of cluster chain" {
const fat_config = FATConfig{
.bytes_per_sector = 16,
.sectors_per_cluster = 1,
.reserved_sectors = 0,
.hidden_sectors = undefined,
.total_sectors = undefined,
.sectors_per_fat = 1,
.root_directory_cluster = undefined,
.fsinfo_sector = undefined,
.backup_boot_sector = undefined,
.has_fs_info = undefined,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = 0x0FFFFFFF,
};
var buff_stream = [_]u8{
// FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
// Backup FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
// Data region cluster 1
'a', 'b', 'c', 'd',
'1', '2', '3', '4',
'A', 'B', 'C', 'D',
'!', '"', '$', '%',
// Data region cluster 2
'e', 'f', 'g', 'h',
'5', '6', '7', '8',
'E', 'F', 'G', 'H',
'^', '&', '*', '(',
};
var stream = &std.io.fixedBufferStream(buff_stream[0..]);
var cluster_chain = try Fat32FS(@TypeOf(stream)).ClusterChainIterator.init(std.testing.allocator, fat_config, 2, stream);
defer cluster_chain.deinit();
var buff: [16]u8 = undefined;
_ = try cluster_chain.read(buff[0..]);
var it = Fat32FS(@TypeOf(cluster_chain.stream)).EntryIterator{
.allocator = undefined,
.cluster_block = buff[0..],
.index = 16,
.cluster_chain = cluster_chain,
};
expectEqualSlices(u8, it.cluster_block, "abcd1234ABCD!\"$%");
expectError(error.EndClusterChain, it.checkRead());
}
test "EntryIterator.nextImp - end of entries" {
const fat_config = FATConfig{
.bytes_per_sector = 32,
.sectors_per_cluster = 1,
.reserved_sectors = 0,
.hidden_sectors = undefined,
.total_sectors = undefined,
.sectors_per_fat = 1,
.root_directory_cluster = undefined,
.fsinfo_sector = undefined,
.backup_boot_sector = undefined,
.has_fs_info = undefined,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = 0x0FFFFFFF,
};
var buff_stream = [_]u8{
// FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
// Backup FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
// Data region cluster 1
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
// Data region cluster 2
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
};
var stream = &std.io.fixedBufferStream(buff_stream[0..]);
var cluster_chain = try Fat32FS(@TypeOf(stream)).ClusterChainIterator.init(std.testing.allocator, fat_config, 2, stream);
defer cluster_chain.deinit();
var buff: [32]u8 = undefined;
_ = try cluster_chain.read(buff[0..]);
var it = Fat32FS(@TypeOf(cluster_chain.stream)).EntryIterator{
.allocator = undefined,
.cluster_block = buff[0..],
.index = 0,
.cluster_chain = cluster_chain,
};
const actual = try it.nextImp();
expectEqual(actual, null);
}
test "EntryIterator.nextImp - just deleted files" {
const fat_config = FATConfig{
.bytes_per_sector = 32,
.sectors_per_cluster = 1,
.reserved_sectors = 0,
.hidden_sectors = undefined,
.total_sectors = undefined,
.sectors_per_fat = 1,
.root_directory_cluster = undefined,
.fsinfo_sector = undefined,
.backup_boot_sector = undefined,
.has_fs_info = undefined,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = 0x0FFFFFFF,
};
// These deleted files are taken from a real FAT32 implementation. There is one deleted file for cluster 2
// This will also read the next cluster chain.
var buff_stream = [_]u8{
// FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
// Backup FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
// Data region cluster 1
0xE5, 0x41, 0x4D, 0x44,
0x49, 0x53, 0x7E, 0x32,
0x54, 0x58, 0x54, 0x00,
0x18, 0x34, 0x47, 0x76,
0xF9, 0x50, 0x00, 0x00,
0x00, 0x00, 0x48, 0x76,
0xF9, 0x50, 0x04, 0x00,
0x24, 0x00, 0x00, 0x00,
// Data region cluster 2
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
};
var stream = &std.io.fixedBufferStream(buff_stream[0..]);
var cluster_chain = try Fat32FS(@TypeOf(stream)).ClusterChainIterator.init(std.testing.allocator, fat_config, 2, stream);
defer cluster_chain.deinit();
var buff: [32]u8 = undefined;
_ = try cluster_chain.read(buff[0..]);
var it = Fat32FS(@TypeOf(cluster_chain.stream)).EntryIterator{
.allocator = undefined,
.cluster_block = buff[0..],
.index = 0,
.cluster_chain = cluster_chain,
};
const actual = try it.nextImp();
expectEqual(actual, null);
}
test "EntryIterator.nextImp - short name only" {
const fat_config = FATConfig{
.bytes_per_sector = 32,
.sectors_per_cluster = 1,
.reserved_sectors = 0,
.hidden_sectors = undefined,
.total_sectors = undefined,
.sectors_per_fat = 1,
.root_directory_cluster = undefined,
.fsinfo_sector = undefined,
.backup_boot_sector = undefined,
.has_fs_info = undefined,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = 0x0FFFFFFF,
};
// This short name files are taken from a real FAT32 implementation.
// This will also read the next cluster chain.
var buff_stream = [_]u8{
// FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
// Backup FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
// Data region cluster 1
0x42, 0x53, 0x48, 0x4F,
0x52, 0x54, 0x20, 0x20,
0x54, 0x58, 0x54, 0x00,
0x10, 0xA0, 0x68, 0xA9,
0xFE, 0x50, 0x00, 0x00,
0x00, 0x00, 0x6E, 0xA9,
0xFE, 0x50, 0x04, 0x00,
0x13, 0x00, 0x00, 0x00,
// Data region cluster 2
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
};
var stream = &std.io.fixedBufferStream(buff_stream[0..]);
var cluster_chain = try Fat32FS(@TypeOf(stream)).ClusterChainIterator.init(std.testing.allocator, fat_config, 2, stream);
defer cluster_chain.deinit();
var buff: [32]u8 = undefined;
_ = try cluster_chain.read(buff[0..]);
var it = Fat32FS(@TypeOf(cluster_chain.stream)).EntryIterator{
.allocator = undefined,
.cluster_block = buff[0..],
.index = 0,
.cluster_chain = cluster_chain,
};
const actual = (try it.nextImp()) orelse return error.TestFail;
defer actual.deinit();
expectEqualSlices(u8, actual.short_name.getSFNName()[0..], "BSHORT TXT");
expectEqual(actual.long_name, null);
}
test "EntryIterator.nextImp - long name only" {
const fat_config = FATConfig{
.bytes_per_sector = 32,
.sectors_per_cluster = 1,
.reserved_sectors = 0,
.hidden_sectors = undefined,
.total_sectors = undefined,
.sectors_per_fat = 1,
.root_directory_cluster = undefined,
.fsinfo_sector = undefined,
.backup_boot_sector = undefined,
.has_fs_info = undefined,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = 0x0FFFFFFF,
};
// This short name files are taken from a real FAT32 implementation.
// This will also read the next cluster chain.
// FAT 2 long then blank
// FAT 4 2 long entries, no associated short
var buff_stream = [_]u8{
// FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF,
0x05, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
// Backup FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF,
0x05, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
// Data region cluster 1
0x41, 0x42, 0x00, 0x73,
0x00, 0x68, 0x00, 0x6F,
0x00, 0x72, 0x00, 0x0F,
0x00, 0xA8, 0x74, 0x00,
0x2E, 0x00, 0x74, 0x00,
0x78, 0x00, 0x74, 0x00,
0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF,
// Data region cluster 2
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
// Data region cluster 3
0x41, 0x42, 0x00, 0x73,
0x00, 0x68, 0x00, 0x6F,
0x00, 0x72, 0x00, 0x0F,
0x00, 0xA8, 0x74, 0x00,
0x2E, 0x00, 0x74, 0x00,
0x78, 0x00, 0x74, 0x00,
0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF,
// Data region cluster 4
0x41, 0x42, 0x00, 0x73,
0x00, 0x68, 0x00, 0x6F,
0x00, 0x72, 0x00, 0x0F,
0x00, 0xA8, 0x74, 0x00,
0x2E, 0x00, 0x74, 0x00,
0x78, 0x00, 0x74, 0x00,
0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF,
};
// FAT 2 test
{
var stream = &std.io.fixedBufferStream(buff_stream[0..]);
var cluster_chain = try Fat32FS(@TypeOf(stream)).ClusterChainIterator.init(std.testing.allocator, fat_config, 2, stream);
defer cluster_chain.deinit();
var buff: [32]u8 = undefined;
_ = try cluster_chain.read(buff[0..]);
var it = Fat32FS(@TypeOf(cluster_chain.stream)).EntryIterator{
.allocator = std.testing.allocator,
.cluster_block = buff[0..],
.index = 0,
.cluster_chain = cluster_chain,
};
expectError(error.Orphan, it.nextImp());
}
// FAT 4 test
{
var stream = &std.io.fixedBufferStream(buff_stream[0..]);
var cluster_chain = try Fat32FS(@TypeOf(stream)).ClusterChainIterator.init(std.testing.allocator, fat_config, 4, stream);
defer cluster_chain.deinit();
var buff: [32]u8 = undefined;
_ = try cluster_chain.read(buff[0..]);
var it = Fat32FS(@TypeOf(cluster_chain.stream)).EntryIterator{
.allocator = std.testing.allocator,
.cluster_block = buff[0..],
.index = 0,
.cluster_chain = cluster_chain,
};
expectError(error.Orphan, it.nextImp());
}
}
test "EntryIterator.nextImp - long name, incorrect check sum" {
const fat_config = FATConfig{
.bytes_per_sector = 32,
.sectors_per_cluster = 1,
.reserved_sectors = 0,
.hidden_sectors = undefined,
.total_sectors = undefined,
.sectors_per_fat = 1,
.root_directory_cluster = undefined,
.fsinfo_sector = undefined,
.backup_boot_sector = undefined,
.has_fs_info = undefined,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = 0x0FFFFFFF,
};
// Values taken from a real FAT32 implementation
// In data region cluster 1, row 4 column 2,
// this has changed from the valid 0xA8 to a invalid 0x55 check sum
var buff_stream = [_]u8{
// FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
// Backup FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
// Data region cluster 1
0x41, 0x42, 0x00, 0x73,
0x00, 0x68, 0x00, 0x6F,
0x00, 0x72, 0x00, 0x0F,
0x00, 0x55, 0x74, 0x00,
0x2E, 0x00, 0x74, 0x00,
0x78, 0x00, 0x74, 0x00,
0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF,
// Data region cluster 2
0x42, 0x53, 0x48, 0x4F,
0x52, 0x54, 0x20, 0x20,
0x54, 0x58, 0x54, 0x00,
0x10, 0xA0, 0x68, 0xA9,
0xFE, 0x50, 0x00, 0x00,
0x00, 0x00, 0x6E, 0xA9,
0xFE, 0x50, 0x04, 0x00,
0x13, 0x00, 0x00, 0x00,
// Data region cluster 3
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
};
var stream = &std.io.fixedBufferStream(buff_stream[0..]);
var cluster_chain = try Fat32FS(@TypeOf(stream)).ClusterChainIterator.init(std.testing.allocator, fat_config, 2, stream);
defer cluster_chain.deinit();
var buff: [32]u8 = undefined;
_ = try cluster_chain.read(buff[0..]);
var it = Fat32FS(@TypeOf(cluster_chain.stream)).EntryIterator{
.allocator = std.testing.allocator,
.cluster_block = buff[0..],
.index = 0,
.cluster_chain = cluster_chain,
};
expectError(error.Orphan, it.nextImp());
}
test "EntryIterator.nextImp - long name missing entry" {
// 0x43
// 0x01
// missing 0x02
const fat_config = FATConfig{
.bytes_per_sector = 32,
.sectors_per_cluster = 1,
.reserved_sectors = 0,
.hidden_sectors = undefined,
.total_sectors = undefined,
.sectors_per_fat = 1,
.root_directory_cluster = undefined,
.fsinfo_sector = undefined,
.backup_boot_sector = undefined,
.has_fs_info = undefined,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = 0x0FFFFFFF,
};
// Values taken from a real FAT32 implementation
// In data region cluster 1, row 4 column 2,
// this has changed from the valid 0xA8 to a invalid 0x55 check sum
var buff_stream = [_]u8{
// FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
// Backup FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
// Data region cluster 1
0x43, 0x6E, 0x00, 0x67,
0x00, 0x6E, 0x00, 0x61,
0x00, 0x6D, 0x00, 0x0F,
0x00, 0x6E, 0x65, 0x00,
0x2E, 0x00, 0x74, 0x00,
0x78, 0x00, 0x74, 0x00,
0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF,
// Data region cluster 2
0x01, 0x6C, 0x00, 0x6F,
0x00, 0x6F, 0x00, 0x6F,
0x00, 0x6F, 0x00, 0x0F,
0x00, 0x6E, 0x6F, 0x00,
0x6E, 0x00, 0x67, 0x00,
0x6C, 0x00, 0x6F, 0x00,
0x6F, 0x00, 0x00, 0x00,
0x6F, 0x00, 0x6F, 0x00,
// Data region cluster 3
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
};
var stream = &std.io.fixedBufferStream(buff_stream[0..]);
var cluster_chain = try Fat32FS(@TypeOf(stream)).ClusterChainIterator.init(std.testing.allocator, fat_config, 2, stream);
defer cluster_chain.deinit();
var buff: [32]u8 = undefined;
_ = try cluster_chain.read(buff[0..]);
var it = Fat32FS(@TypeOf(cluster_chain.stream)).EntryIterator{
.allocator = std.testing.allocator,
.cluster_block = buff[0..],
.index = 0,
.cluster_chain = cluster_chain,
};
expectError(error.Orphan, it.nextImp());
}
test "EntryIterator.nextImp - valid short and long entry" {
const fat_config = FATConfig{
.bytes_per_sector = 32,
.sectors_per_cluster = 1,
.reserved_sectors = 0,
.hidden_sectors = undefined,
.total_sectors = undefined,
.sectors_per_fat = 1,
.root_directory_cluster = undefined,
.fsinfo_sector = undefined,
.backup_boot_sector = undefined,
.has_fs_info = undefined,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = 0x0FFFFFFF,
};
// Values taken from a real FAT32 implementation
var buff_stream = [_]u8{
// FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
// Backup FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
// Data region cluster 1
0x43, 0x6E, 0x00, 0x67,
0x00, 0x6E, 0x00, 0x61,
0x00, 0x6D, 0x00, 0x0F,
0x00, 0x6E, 0x65, 0x00,
0x2E, 0x00, 0x74, 0x00,
0x78, 0x00, 0x74, 0x00,
0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF,
// Data region cluster 2
0x02, 0x6E, 0x00, 0x67,
0x00, 0x76, 0x00, 0x65,
0x00, 0x72, 0x00, 0x0F,
0x00, 0x6E, 0x79, 0x00,
0x6C, 0x00, 0x6F, 0x00,
0x6F, 0x00, 0x6F, 0x00,
0x6F, 0x00, 0x00, 0x00,
0x6F, 0x00, 0x6F, 0x00,
// Data region cluster 3
0x01, 0x6C, 0x00, 0x6F,
0x00, 0x6F, 0x00, 0x6F,
0x00, 0x6F, 0x00, 0x0F,
0x00, 0x6E, 0x6F, 0x00,
0x6E, 0x00, 0x67, 0x00,
0x6C, 0x00, 0x6F, 0x00,
0x6F, 0x00, 0x00, 0x00,
0x6F, 0x00, 0x6F, 0x00,
// Data region cluster 4
0x4C, 0x4F, 0x4F, 0x4F,
0x4F, 0x4F, 0x7E, 0x31,
0x54, 0x58, 0x54, 0x00,
0x18, 0xA0, 0x68, 0xA9,
0xFE, 0x50, 0x00, 0x00,
0x00, 0x00, 0x6E, 0xA9,
0xFE, 0x50, 0x08, 0x00,
0x13, 0x00, 0x00, 0x00,
};
var stream = &std.io.fixedBufferStream(buff_stream[0..]);
var cluster_chain = try Fat32FS(@TypeOf(stream)).ClusterChainIterator.init(std.testing.allocator, fat_config, 2, stream);
defer cluster_chain.deinit();
var buff: [32]u8 = undefined;
_ = try cluster_chain.read(buff[0..]);
var it = Fat32FS(@TypeOf(cluster_chain.stream)).EntryIterator{
.allocator = std.testing.allocator,
.cluster_block = buff[0..],
.index = 0,
.cluster_chain = cluster_chain,
};
const actual = (try it.nextImp()) orelse return error.TestFail;
defer actual.deinit();
expectEqualSlices(u8, actual.short_name.getSFNName()[0..], "LOOOOO~1TXT");
expectEqualSlices(u8, actual.long_name.?, "looooongloooongveryloooooongname.txt");
}
test "EntryIterator.next - skips orphan long entry" {
const fat_config = FATConfig{
.bytes_per_sector = 32,
.sectors_per_cluster = 1,
.reserved_sectors = 0,
.hidden_sectors = undefined,
.total_sectors = undefined,
.sectors_per_fat = 1,
.root_directory_cluster = undefined,
.fsinfo_sector = undefined,
.backup_boot_sector = undefined,
.has_fs_info = undefined,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = 0x0FFFFFFF,
};
// Values taken from a real FAT32 implementation
var buff_stream = [_]u8{
// FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF,
// Backup FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF,
// Data region cluster 1
0x43, 0x6E, 0x00, 0x67,
0x00, 0x6E, 0x00, 0x61,
0x00, 0x6D, 0x00, 0x0F,
0x00, 0x6E, 0x65, 0x00,
0x2E, 0x00, 0x74, 0x00,
0x78, 0x00, 0x74, 0x00,
0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF,
// Missing 0x02
// Data region cluster 2
0x01, 0x6C, 0x00, 0x6F,
0x00, 0x6F, 0x00, 0x6F,
0x00, 0x6F, 0x00, 0x0F,
0x00, 0x6E, 0x6F, 0x00,
0x6E, 0x00, 0x67, 0x00,
0x6C, 0x00, 0x6F, 0x00,
0x6F, 0x00, 0x00, 0x00,
0x6F, 0x00, 0x6F, 0x00,
// Data region cluster 3
0x4C, 0x4F, 0x4F, 0x4F,
0x4F, 0x4F, 0x7E, 0x31,
0x54, 0x58, 0x54, 0x00,
0x18, 0xA0, 0x68, 0xA9,
0xFE, 0x50, 0x00, 0x00,
0x00, 0x00, 0x6E, 0xA9,
0xFE, 0x50, 0x08, 0x00,
0x13, 0x00, 0x00, 0x00,
// Data region cluster 4
0x42, 0x2E, 0x00, 0x74,
0x00, 0x78, 0x00, 0x74,
0x00, 0x00, 0x00, 0x0F,
0x00, 0xE9, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF,
// Data region cluster 5
0x01, 0x72, 0x00, 0x61,
0x00, 0x6D, 0x00, 0x64,
0x00, 0x69, 0x00, 0x0F,
0x00, 0xE9, 0x73, 0x00,
0x6B, 0x00, 0x5F, 0x00,
0x74, 0x00, 0x65, 0x00,
0x73, 0x00, 0x00, 0x00,
0x74, 0x00, 0x31, 0x00,
// Data region cluster 6
0x52, 0x41, 0x4D, 0x44,
0x49, 0x53, 0x7E, 0x31,
0x54, 0x58, 0x54, 0x00,
0x18, 0x34, 0x47, 0x76,
0xF9, 0x50, 0x00, 0x00,
0x00, 0x00, 0x48, 0x76,
0xF9, 0x50, 0x03, 0x00,
0x10, 0x00, 0x00, 0x00,
};
var stream = &std.io.fixedBufferStream(buff_stream[0..]);
var cluster_chain = try Fat32FS(@TypeOf(stream)).ClusterChainIterator.init(std.testing.allocator, fat_config, 2, stream);
defer cluster_chain.deinit();
var buff: [32]u8 = undefined;
_ = try cluster_chain.read(buff[0..]);
var it = Fat32FS(@TypeOf(cluster_chain.stream)).EntryIterator{
.allocator = std.testing.allocator,
.cluster_block = buff[0..],
.index = 0,
.cluster_chain = cluster_chain,
};
const actual1 = (try it.next()) orelse return error.TestFail;
defer actual1.deinit();
expectEqualSlices(u8, actual1.short_name.getSFNName()[0..], "LOOOOO~1TXT");
expectEqual(actual1.long_name, null);
const actual2 = (try it.next()) orelse return error.TestFail;
defer actual2.deinit();
expectEqualSlices(u8, actual2.short_name.getSFNName()[0..], "RAMDIS~1TXT");
expectEqualSlices(u8, actual2.long_name.?, "ramdisk_test1.txt");
expectEqual(try it.next(), null);
}
test "EntryIterator.init - free on OutOfMemory" {
const fat_config = FATConfig{
.bytes_per_sector = 16,
.sectors_per_cluster = 1,
.reserved_sectors = 0,
.hidden_sectors = undefined,
.total_sectors = undefined,
.sectors_per_fat = 1,
.root_directory_cluster = undefined,
.fsinfo_sector = undefined,
.backup_boot_sector = undefined,
.has_fs_info = undefined,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = 0x0FFFFFFF,
};
var buff_stream = [_]u8{
// FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
// Backup FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
// Data region
'a', 'b', 'c', 'd',
'1', '2', '3', '4',
'A', 'B', 'C', 'D',
'!', '"', '$', '%',
};
var stream = &std.io.fixedBufferStream(buff_stream[0..]);
const allocations: usize = 2;
var i: usize = 0;
while (i < allocations) : (i += 1) {
var fa = std.testing.FailingAllocator.init(std.testing.allocator, i);
expectError(error.OutOfMemory, Fat32FS(@TypeOf(stream)).EntryIterator.init(&fa.allocator, fat_config, 2, stream));
}
}
test "EntryIterator.init - free on BadRead" {
const fat_config = FATConfig{
.bytes_per_sector = 16,
.sectors_per_cluster = 1,
.reserved_sectors = 0,
.hidden_sectors = undefined,
.total_sectors = undefined,
.sectors_per_fat = 1,
.root_directory_cluster = undefined,
.fsinfo_sector = undefined,
.backup_boot_sector = undefined,
.has_fs_info = undefined,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = 0x0FFFFFFF,
};
var buff_stream = [_]u8{
// FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
// Backup FAT region
0xFF, 0xFF, 0xFF, 0x0F,
0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
// Data region (too short)
'a', 'b', 'c', 'd',
'1', '2', '3', '4',
};
var stream = &std.io.fixedBufferStream(buff_stream[0..]);
expectError(error.BadRead, Fat32FS(@TypeOf(stream)).EntryIterator.init(std.testing.allocator, fat_config, 2, stream));
}
test "Fat32FS.createNode - free memory" {
const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{});
defer test_fat32_image.close();
var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image);
defer test_fs.destroy();
// There 2 allocations
var allocations: usize = 0;
while (allocations < 2) : (allocations += 1) {
var fa = std.testing.FailingAllocator.init(std.testing.allocator, allocations);
const allocator = &fa.allocator;
test_fs.allocator = allocator;
expectError(error.OutOfMemory, test_fs.createNode(3, 16, .CREATE_FILE, .{}));
}
}
test "Fat32FS.getDirCluster - root dir" {
const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{});
defer test_fat32_image.close();
var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image);
defer test_fs.destroy();
var test_node_1 = try test_fs.createNode(3, 16, .CREATE_FILE, .{});
defer test_node_1.File.close();
const actual = try test_fs.getDirCluster(&test_fs.root_node.node.Dir);
expectEqual(actual, 2);
}
test "Fat32FS.getDirCluster - sub dir" {
const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{});
defer test_fat32_image.close();
var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image);
defer test_fs.destroy();
var test_node_1 = try test_fs.createNode(5, 0, .CREATE_DIR, .{});
defer test_node_1.Dir.close();
const actual = try test_fs.getDirCluster(&test_node_1.Dir);
expectEqual(actual, 5);
}
test "Fat32FS.getDirCluster - not opened dir" {
const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{});
defer test_fat32_image.close();
var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image);
defer test_fs.destroy();
var test_node_1 = try test_fs.createNode(5, 0, .CREATE_DIR, .{});
const elem = test_fs.opened_files.remove(test_node_1).?.value;
std.testing.allocator.destroy(elem);
expectError(error.NotOpened, test_fs.getDirCluster(&test_node_1.Dir));
std.testing.allocator.destroy(test_node_1);
}
test "Fat32FS.openImpl - entry iterator failed init" {
const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{});
defer test_fat32_image.close();
var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image);
defer test_fs.destroy();
var test_node_1 = try test_fs.createNode(5, 0, .CREATE_DIR, .{});
defer test_node_1.Dir.close();
var fa = std.testing.FailingAllocator.init(std.testing.allocator, 1);
const allocator = &fa.allocator;
test_fs.allocator = allocator;
expectError(error.OutOfMemory, Fat32FS(@TypeOf(test_fat32_image)).openImpl(test_fs.fs, &test_node_1.Dir, "file.txt"));
}
test "Fat32FS.openImpl - entry iterator failed next" {
const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{});
defer test_fat32_image.close();
var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image);
defer test_fs.destroy();
var fa = std.testing.FailingAllocator.init(std.testing.allocator, 2);
const allocator = &fa.allocator;
test_fs.allocator = allocator;
expectError(error.OutOfMemory, Fat32FS(@TypeOf(test_fat32_image)).openImpl(test_fs.fs, &test_fs.root_node.node.Dir, "short.txt"));
}
test "Fat32FS.openImpl - entry iterator failed 2nd next" {
const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{});
defer test_fat32_image.close();
var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image);
defer test_fs.destroy();
var fa = std.testing.FailingAllocator.init(std.testing.allocator, 3);
const allocator = &fa.allocator;
test_fs.allocator = allocator;
expectError(error.OutOfMemory, Fat32FS(@TypeOf(test_fat32_image)).openImpl(test_fs.fs, &test_fs.root_node.node.Dir, "short.txt"));
}
test "Fat32FS.openImpl - match short name" {
const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{});
defer test_fat32_image.close();
var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image);
defer test_fs.destroy();
const file_node = try Fat32FS(@TypeOf(test_fat32_image)).openImpl(test_fs.fs, &test_fs.root_node.node.Dir, "INSANE~1.TXT");
defer file_node.File.close();
}
test "Fat32FS.openImpl - match long name" {
const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{});
defer test_fat32_image.close();
var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image);
defer test_fs.destroy();
const file_node = try Fat32FS(@TypeOf(test_fat32_image)).openImpl(test_fs.fs, &test_fs.root_node.node.Dir, "insanely_long_insanely_long_insanely_long_insanely_long_insanely_long_insanely_long_insanely_long_insanely_long_insanely_long_insanely_long_insanely_long_insanely_long_insanely_long_insanely_long_insanely_long_insanely_long.txt");
defer file_node.File.close();
}
test "Fat32FS.openImpl - no match" {
const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{});
defer test_fat32_image.close();
var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image);
defer test_fs.destroy();
var test_node_1 = try test_fs.createNode(5, 0, .CREATE_DIR, .{});
defer test_node_1.Dir.close();
expectError(vfs.Error.NoSuchFileOrDir, Fat32FS(@TypeOf(test_fat32_image)).openImpl(test_fs.fs, &test_node_1.Dir, "file.txt"));
}
test "Fat32FS.open - no create - hand crafted" {
var test_file_buf = try std.testing.allocator.alloc(u8, 1024 * 1024);
defer std.testing.allocator.free(test_file_buf);
var stream = &std.io.fixedBufferStream(test_file_buf[0..]);
try mkfat32.Fat32.make(.{}, stream, true);
var test_fs = try initialiseFAT32(std.testing.allocator, stream);
defer test_fs.destroy();
var entry_buff = [_]u8{
// Long entry 3
0x43, 0x6E, 0x00, 0x67, 0x00, 0x6E, 0x00, 0x61, 0x00, 0x6D, 0x00, 0x0F, 0x00, 0x6E, 0x65, 0x00,
0x2E, 0x00, 0x74, 0x00, 0x78, 0x00, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
// Long entry 2
0x02, 0x6E, 0x00, 0x67, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x0F, 0x00, 0x6E, 0x79, 0x00,
0x6C, 0x00, 0x6F, 0x00, 0x6F, 0x00, 0x6F, 0x00, 0x6F, 0x00, 0x00, 0x00, 0x6F, 0x00, 0x6F, 0x00,
// Long entry 1
0x01, 0x6C, 0x00, 0x6F, 0x00, 0x6F, 0x00, 0x6F, 0x00, 0x6F, 0x00, 0x0F, 0x00, 0x6E, 0x6F, 0x00,
0x6E, 0x00, 0x67, 0x00, 0x6C, 0x00, 0x6F, 0x00, 0x6F, 0x00, 0x00, 0x00, 0x6F, 0x00, 0x6F, 0x00,
// Short entry
0x4C, 0x4F, 0x4F, 0x4F, 0x4F, 0x4F, 0x7E, 0x31, 0x54, 0x58, 0x54, 0x00, 0x18, 0xA0, 0x68, 0xA9,
0xFE, 0x50, 0x00, 0x00, 0x00, 0x00, 0x6E, 0xA9, 0xFE, 0x50, 0x08, 0x00, 0x13, 0x00, 0x00, 0x00,
// Long entry 2
0x42, 0x2E, 0x00, 0x74, 0x00, 0x78, 0x00, 0x74, 0x00, 0x00, 0x00, 0x0F, 0x00, 0xE9, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
// Long entry 1
0x01, 0x72, 0x00, 0x61, 0x00, 0x6D, 0x00, 0x64, 0x00, 0x69, 0x00, 0x0F, 0x00, 0xE9, 0x73, 0x00,
0x6B, 0x00, 0x5F, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x00, 0x00, 0x74, 0x00, 0x31, 0x00,
// Short entry
0x52, 0x41, 0x4D, 0x44, 0x49, 0x53, 0x7E, 0x31, 0x54, 0x58, 0x54, 0x00, 0x18, 0x34, 0x47, 0x76,
0xF9, 0x50, 0x00, 0x00, 0x00, 0x00, 0x48, 0x76, 0xF9, 0x50, 0x03, 0x00, 0x10, 0x00, 0x00, 0x00,
};
// Goto root dir and write a long and short entry
const sector = test_fs.fat_config.clusterToSector(test_fs.root_node.cluster);
try test_fs.stream.seekableStream().seekTo(sector * test_fs.fat_config.bytes_per_sector);
try test_fs.stream.writer().writeAll(entry_buff[0..]);
vfs.setRoot(test_fs.root_node.node);
const file = try vfs.openFile("/ramdisk_test1.txt", .NO_CREATION);
defer file.close();
expect(test_fs.opened_files.contains(@ptrCast(*const vfs.Node, file)));
const opened_info = test_fs.opened_files.get(@ptrCast(*const vfs.Node, file)).?;
expectEqual(opened_info.cluster, 3);
expectEqual(opened_info.size, 16);
}
test "Fat32FS.open - no create - all files" {
const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{});
defer test_fat32_image.close();
var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image);
defer test_fs.destroy();
vfs.setRoot(test_fs.root_node.node);
// Check we can open all the expected files correctly
var test_files = try std.fs.cwd().openDir("test/fat32/test_files", .{ .iterate = true });
defer test_files.close();
var it = test_files.iterate();
while (try it.next()) |file| {
// Need to add a '/' at the beginning
var file_name = try std.testing.allocator.alloc(u8, file.name.len + 1);
defer std.testing.allocator.free(file_name);
file_name[0] = '/';
std.mem.copy(u8, file_name[1..], file.name);
const open_file = try vfs.openFile(file_name, .NO_CREATION);
defer open_file.close();
}
}
test "Fat32FS.open - create file" {
// TODO: Once the open and write PR is done
}
test "Fat32FS.open - create directory" {
// TODO: Once the open and write PR is done
}
test "Fat32FS.open - create symlink" {
// TODO: Once the open and write PR is done
}
test "Fat32FS.read - not opened" {
const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{});
defer test_fat32_image.close();
var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image);
defer test_fs.destroy();
// Craft a node
var node = try std.testing.allocator.create(vfs.Node);
defer std.testing.allocator.destroy(node);
node.* = .{ .File = .{ .fs = test_fs.fs } };
expectError(error.NotOpened, node.File.read(&[_]u8{}));
}
test "Fat32FS.read - cluster iterator init fail" {
const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{});
defer test_fat32_image.close();
var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image);
defer test_fs.destroy();
var test_node = try test_fs.createNode(5, 16, .CREATE_FILE, .{});
defer test_node.File.close();
var fa = std.testing.FailingAllocator.init(std.testing.allocator, 0);
const allocator = &fa.allocator;
test_fs.allocator = allocator;
var buff = [_]u8{0xAA} ** 128;
expectError(error.OutOfMemory, test_node.File.read(buff[0..]));
}
test "Fat32FS.read - buffer smaller than file" {
const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{});
defer test_fat32_image.close();
var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image);
defer test_fs.destroy();
vfs.setRoot(test_fs.root_node.node);
const test_node = try vfs.openFile("/short.txt", .NO_CREATION);
defer test_node.close();
var buff = [_]u8{0xAA} ** 8;
const read = try test_node.read(buff[0..]);
expectEqualSlices(u8, buff[0..read], "short.tx");
}
test "Fat32FS.read - buffer bigger than file" {
const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{});
defer test_fat32_image.close();
var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image);
defer test_fs.destroy();
vfs.setRoot(test_fs.root_node.node);
const test_node = try vfs.openFile("/short.txt", .NO_CREATION);
defer test_node.close();
var buff = [_]u8{0xAA} ** 16;
const read = try test_node.read(buff[0..]);
expectEqualSlices(u8, buff[0..read], "short.txt");
// The rest should be unchanged
expectEqualSlices(u8, buff[read..], &[_]u8{0xAA} ** 7);
}
test "Fat32FS.read - large" {
const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{});
defer test_fat32_image.close();
var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image);
defer test_fs.destroy();
vfs.setRoot(test_fs.root_node.node);
const test_node = try vfs.openFile("/large_file2.txt", .NO_CREATION);
defer test_node.close();
var buff = [_]u8{0xAA} ** 8450;
const read = try test_node.read(buff[0..]);
expectEqual(read, 8450);
const large_file_content = @embedFile("../../../test/fat32/test_files/large_file2.txt");
expectEqualSlices(u8, buff[0..], large_file_content[0..]);
}
test "Fat32FS.read - all test files" {
const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{});
defer test_fat32_image.close();
var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image);
defer test_fs.destroy();
vfs.setRoot(test_fs.root_node.node);
// Check we can open all the expected files correctly
var test_files = try std.fs.cwd().openDir("test/fat32/test_files", .{ .iterate = true });
defer test_files.close();
var it = test_files.iterate();
while (try it.next()) |file| {
// Need to add a '/' at the beginning
var file_name = try std.testing.allocator.alloc(u8, file.name.len + 1);
defer std.testing.allocator.free(file_name);
file_name[0] = '/';
std.mem.copy(u8, file_name[1..], file.name);
const open_file = try vfs.openFile(file_name, .NO_CREATION);
defer open_file.close();
// Big enough
var buff = [_]u8{0xAA} ** 256;
const read = try open_file.read(buff[0..]);
// The file content is the same as the file name
expectEqual(file_name.len - 1, read);
expectEqualSlices(u8, buff[0..read], file_name[1..]);
}
}
test "Fat32FS.write" {
// TODO: Once the write PR is done
}