pluto/src/kernel/filesystem/fat32.zig
2022-03-06 15:22:34 +00:00

6090 lines
248 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 = std.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 arch = @import("../arch.zig").internals;
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 short 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;
}
///
/// Initialise a slice with the values from a struct.
/// TODO: Once packed structs are good to go, then use std.mem.bytesAsValue.
///
/// Arguments:
/// IN comptime Type: type - The type of the struct.
/// IN copy_struct: Type - The struct to copy from.
/// IN bytes: []u8 - The bytes to copy to.
///
///
fn initBytes(comptime Type: type, copy_struct: Type, bytes: []u8) void {
comptime var index = 0;
inline for (std.meta.fields(Type)) |item| {
switch (item.field_type) {
u8 => bytes[index] = @field(copy_struct, item.name),
u16 => std.mem.bytesAsSlice(u16, bytes[index .. index + 2])[0] = @field(copy_struct, item.name),
u32 => std.mem.bytesAsSlice(u32, bytes[index .. index + 4])[0] = @field(copy_struct, item.name),
else => {
switch (@typeInfo(item.field_type)) {
.Array => |info| switch (info.child) {
u8 => {
comptime var i = 0;
inline while (i < info.len) : (i += 1) {
bytes[index + i] = @field(copy_struct, item.name)[i];
}
},
u16 => {
comptime var i = 0;
inline while (i < info.len) : (i += 1) {
std.mem.bytesAsSlice(u16, bytes[index + (i * 2) .. index + 2 + (i * 2)])[0] = @field(copy_struct, item.name)[i];
}
},
else => @compileError("Unexpected field type: " ++ @typeName(info.child)),
},
else => @compileError("Unexpected field type: " ++ @typeName(item.field_type)),
}
},
}
index += @sizeOf(item.field_type);
}
}
///
/// 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,
// TODO: Have a FAT cache to not touching disk so much
// If then need to read a new part of the FAT, then flush the old one.
// Have a pub fn so the user can flush everything.
/// 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 cluster at which the FAT dir short entry for this node is located.
entry_cluster: u32,
/// The offset within the entry_cluster the short entry is located.
entry_offset: 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,
/// When there is is no more space on the stream for a new entry.
DiskFull,
/// When destroying the filesystem, this is returned if there are filles left open.
FilesStillOpen,
};
/// 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 and reading the cluster data.
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) {
// 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: *const 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);
const table_offset = cluster / fat.len;
// 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
if (self.cluster_block[self.index + 11] == 0x0F and self.cluster_block[self.index] & 0x40 == 0x40) {
// 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 + 11] == 0x0F and self.cluster_block[self.index] != 0x00) {
// 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.fetchRemove(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;
// Short cut if length is less than cluster size, can just write the content directly without modifying the FAT
if (bytes.len <= self.fat_config.sectors_per_cluster * self.fat_config.bytes_per_sector) {
const sector = self.fat_config.clusterToSector(opened_node.cluster);
self.stream.seekableStream().seekTo(sector * self.fat_config.bytes_per_sector) catch return vfs.Error.Unexpected;
_ = self.stream.writer().writeAll(bytes) catch return vfs.Error.Unexpected;
} else {
var to_write: u32 = self.fat_config.sectors_per_cluster * self.fat_config.bytes_per_sector;
var write_index: u32 = 0;
var next_free_cluster: u32 = opened_node.cluster;
if (self.fat_config.has_fs_info) {
if (self.fat_config.number_free_clusters < bytes.len / (self.fat_config.sectors_per_cluster * self.fat_config.bytes_per_sector)) {
// Not enough free clusters
return vfs.Error.Unexpected;
}
}
while (write_index < bytes.len) : ({
write_index = to_write;
to_write = std.math.min(bytes.len, write_index + self.fat_config.sectors_per_cluster * self.fat_config.bytes_per_sector);
if (write_index < bytes.len) {
next_free_cluster = self.findNextFreeCluster(next_free_cluster, next_free_cluster) catch return vfs.Error.Unexpected;
}
}) {
const sector = self.fat_config.clusterToSector(next_free_cluster);
self.stream.seekableStream().seekTo(sector * self.fat_config.bytes_per_sector) catch return vfs.Error.Unexpected;
_ = self.stream.writer().writeAll(bytes[write_index..to_write]) catch return vfs.Error.Unexpected;
}
}
// Update the entry the size for the file.
const entry_sector = self.fat_config.clusterToSector(opened_node.entry_cluster);
self.stream.seekableStream().seekTo(entry_sector * self.fat_config.bytes_per_sector + opened_node.entry_offset) catch return vfs.Error.Unexpected;
self.stream.writer().writeIntLittle(u32, bytes.len) catch return vfs.Error.Unexpected;
opened_node.size = bytes.len;
return bytes.len;
}
/// 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 {
// Suppress unused var warning
_ = open_args;
return switch (flags) {
.NO_CREATION => openImpl(fs, dir, name),
.CREATE_FILE => createFileOrDir(fs, dir, name, false),
.CREATE_DIR => createFileOrDir(fs, dir, name, true),
// FAT32 doesn't support symlinks
.CREATE_SYMLINK => vfs.Error.InvalidFlags,
};
}
///
/// Helper function for creating the correct *Node. This can only create a FileNode or
/// DirNode, Symlinks are not supported for FAT32. The arguments are assumed correct: the
/// cluster points to a free block and the size is correct: zero for directories.
///
/// Arguments:
/// IN self: *Fat32Self - Self, needed for the allocator and underlying filesystem
/// IN cluster: u32 - The cluster there the file/directory will be.
/// IN size: u32 - The size of the file or 0 for a directory.
/// IN entry_cluster: u32 - The cluster where the FAT dir entry is located.
/// IN entry_offset: u32 - The offset in the entry_cluster there the entry is located.
/// IN flags: vfs.OpenFlags - The open flags for deciding on the Node type.
///
/// Return: *vfs.Node
/// The VFS Node
///
/// Error: Allocator.Error || vfs.Error
/// Allocator.Error - Not enough memory for allocating the Node
/// vfs.Error.InvalidFlags - Symlinks are not support in FAT32.
///
fn createNode(self: *Fat32Self, cluster: u32, size: u32, entry_cluster: u32, entry_offset: u32, flags: vfs.OpenFlags) (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, .NO_CREATION => return vfs.Error.InvalidFlags,
};
// 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,
.entry_cluster = entry_cluster,
.entry_offset = entry_offset,
};
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: *const 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 previous_cluster = cluster;
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 in next() iterating the entry iterator. Error: {}\n", .{e});
return vfs.Error.Unexpected;
},
}) |entry| {
defer entry.deinit();
if ((it.cluster_chain.cluster & 0x0FFFFFFF) < self.fat_config.cluster_end_marker) {
previous_cluster = it.cluster_chain.cluster;
}
// 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, previous_cluster, it.index - 4, open_type);
}
}
return vfs.Error.NoSuchFileOrDir;
}
///
/// Helper function for finding a free cluster from a given hint. The hint is where to
/// start looking. This will update the FAT and FSInfo accordingly. If a parent cluster
/// is provided, then the cluster chan will be updated so the parent cluster will point to
/// the new cluster.
///
/// Arguments:
/// IN self: *Fat32Self - Self for allocating a free cluster with the current configuration.
/// IN cluster_hint: u32 - The cluster hint to start looking.
/// IN parent_cluster: ?u32 - The parent cluster to update the cluster chain from. Can
/// be null if creating a new cluster for a file/folder.
///
/// Return: u32
/// The next free cluster to use.
///
/// Error: Allocator.Error || ReadError || SeekError || Fat32Self.Error
/// Allocator.Error - Not enough memory for allocating memory.
/// WriteError - Error while updating the FAT with the new cluster.
/// ReadError - Error while reading the stream.
/// SeekError - Error while seeking the stream.
/// Fat32Self.Error.BadRead - Error reading the FAT, not aligned to the sector.
/// Fat32Self.Error.DiskFull - No free clusters.
///
fn findNextFreeCluster(self: *Fat32Self, cluster_hint: u32, parent_cluster: ?u32) (Allocator.Error || WriteError || ReadError || SeekError || Fat32Self.Error)!u32 {
var fat_buff = try self.allocator.alloc(u32, self.fat_config.bytes_per_sector / @sizeOf(u32));
defer self.allocator.free(fat_buff);
var sector_offset = cluster_hint / fat_buff.len;
const reader = self.stream.reader();
const writer = self.stream.writer();
const seeker = self.stream.seekableStream();
try seeker.seekTo((self.fat_config.reserved_sectors + sector_offset) * self.fat_config.bytes_per_sector);
var fat_read = try reader.readAll(std.mem.sliceAsBytes(fat_buff));
if (fat_read != self.fat_config.bytes_per_sector) {
return Fat32Self.Error.BadRead;
}
// Check for a free cluster by checking the FAT for a 0x00000000 entry (free)
var cluster = cluster_hint;
while (fat_buff[cluster - (sector_offset * fat_buff.len)] != 0x00000000) {
cluster += 1;
// Check we are still in the FAT buffer, if not, read the next FAT part
const check_offset = cluster / fat_buff.len;
if (check_offset > sector_offset) {
// Check if the cluster will go outside the FAT
if (check_offset >= self.fat_config.sectors_per_fat) {
// TODO: Will need to wrap as there maybe free clusters before the hint
if (self.fat_config.has_fs_info) {
std.debug.assert(self.fat_config.number_free_clusters == 0);
}
return Fat32Self.Error.DiskFull;
}
sector_offset = check_offset;
try seeker.seekTo((self.fat_config.reserved_sectors + sector_offset) * self.fat_config.bytes_per_sector);
fat_read = try reader.readAll(std.mem.sliceAsBytes(fat_buff));
if (fat_read != fat_buff.len * @sizeOf(u32)) {
return Fat32Self.Error.BadRead;
}
}
}
// Update the FAT for the allocated cluster
if (parent_cluster) |p_cluster| {
try seeker.seekTo((self.fat_config.reserved_sectors * self.fat_config.bytes_per_sector) + (p_cluster * @sizeOf(u32)));
try writer.writeIntLittle(u32, cluster);
}
try seeker.seekTo((self.fat_config.reserved_sectors * self.fat_config.bytes_per_sector) + (cluster * @sizeOf(u32)));
try writer.writeIntLittle(u32, self.fat_config.cluster_end_marker);
// And the backup FAT
if (parent_cluster) |p_cluster| {
try seeker.seekTo(((self.fat_config.sectors_per_fat + self.fat_config.reserved_sectors) * self.fat_config.bytes_per_sector) + (p_cluster * @sizeOf(u32)));
try writer.writeIntLittle(u32, cluster);
}
try seeker.seekTo(((self.fat_config.sectors_per_fat + self.fat_config.reserved_sectors) * self.fat_config.bytes_per_sector) + (cluster * @sizeOf(u32)));
try writer.writeIntLittle(u32, self.fat_config.cluster_end_marker);
// Update the FSInfo if we have one
if (self.fat_config.has_fs_info) {
self.fat_config.next_free_cluster = cluster + 1;
self.fat_config.number_free_clusters -= 1;
// write it to disk
// TODO: Have this cached and flush later to save writes
// Have a flush function so the user can flush and flush on deinit
try seeker.seekTo(self.fat_config.fsinfo_sector * self.fat_config.bytes_per_sector + 488);
try writer.writeIntLittle(u32, self.fat_config.number_free_clusters);
try writer.writeIntLittle(u32, self.fat_config.next_free_cluster);
// And the backup FSInfo
try seeker.seekTo((self.fat_config.backup_boot_sector + self.fat_config.fsinfo_sector) * self.fat_config.bytes_per_sector + 488);
try writer.writeIntLittle(u32, self.fat_config.number_free_clusters);
try writer.writeIntLittle(u32, self.fat_config.next_free_cluster);
}
// Have found a free cluster
return cluster;
}
///
/// 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 is_dir: bool - If creating a file/folder.
///
/// Return: *vfs.Node
/// The VFS Node for the opened/created file/folder.
///
/// Error: Allocator.Error || vfs.Error
/// Allocator.Error - Not enough memory for allocating memory.
/// vfs.Error.NoSuchFileOrDir - Error if creating a symlink and no target is provided.
/// 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 createFileOrDir(fs: *const vfs.FileSystem, dir: *const vfs.DirNode, name: []const u8, is_dir: bool) (Allocator.Error || vfs.Error)!*vfs.Node {
const self = @fieldParentPtr(Fat32Self, "instance", fs.instance);
var files_in_dir = ArrayList([11]u8).init(self.allocator);
defer files_in_dir.deinit();
const dir_cluster = try self.getDirCluster(dir);
var previous_cluster = dir_cluster;
var previous_index: u32 = 0;
var it = EntryIterator.init(self.allocator, self.fat_config, dir_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 in next() iterating the entry iterator. Error: {}\n", .{e});
return vfs.Error.Unexpected;
},
}) |entry| {
defer entry.deinit();
// Keep track of the last cluster before the end
previous_index = it.index;
if ((it.cluster_chain.cluster & 0x0FFFFFFF) < self.fat_config.cluster_end_marker) {
previous_cluster = it.cluster_chain.cluster;
}
try files_in_dir.append(entry.short_name.getSFNName());
}
const existing_files = files_in_dir.toOwnedSlice();
defer self.allocator.free(existing_files);
// Find a free cluster
// The default cluster to start looking for a free cluster
var cluster_hint: u32 = 2;
if (self.fat_config.has_fs_info and self.fat_config.next_free_cluster != 0x0FFFFFFF) {
// Have a next free cluster in the FSInfo, so can use this to start looking
cluster_hint = self.fat_config.next_free_cluster;
}
const free_cluster = self.findNextFreeCluster(cluster_hint, null) catch |e| switch (e) {
error.OutOfMemory => return error.OutOfMemory,
else => {
log.err("Error finding next cluster. Error: {}\n", .{e});
return vfs.Error.Unexpected;
},
};
const dir_attr: ShortName.Attributes = if (is_dir) .Directory else .None;
const entries = createEntries(self.allocator, name, free_cluster, dir_attr, existing_files) catch |e| switch (e) {
error.OutOfMemory => return error.OutOfMemory,
else => {
log.err("Error creating short and long entries. Error: {}\n", .{e});
return vfs.Error.Unexpected;
},
};
defer self.allocator.free(entries.long_entry);
// Write the entries to the directory
const short_offset = self.writeEntries(entries, previous_cluster, free_cluster, previous_index) catch |e| switch (e) {
error.OutOfMemory => return error.OutOfMemory,
else => {
log.err("Error writing entries to disk. Error: {}\n", .{e});
return vfs.Error.Unexpected;
},
};
return self.createNode(free_cluster, 0, short_offset.cluster, short_offset.offset + 28, if (is_dir) .CREATE_DIR else .CREATE_FILE);
}
///
/// Helper function for creating both long and short entries. This will convert the raw
/// name to a valid long and short FAT32 name and create the corresponding FAT32 entries.
/// error.InvalidName will be returned if the raw name cannot be converted to a valid FAT32
/// name. The caller needs to free the long name entries.
///
/// Arguments:
/// IN allocator: Allocator - The allocator for allocating the long entries array.
/// IN name: []const u8 - The raw name to be used for creating the file/directory.
/// IN cluster: u32 - The cluster where the entry will point to.
/// IN attributes: ShortName.Attributes - The attributes of the the entry.
/// IN existing_short_names: []const [11]u8 - Existing short names so to resolve short name clashes.
///
/// Return: FatDirEntry
/// The full FAT entry ready to be copies to disk, byte by byte.
///
/// Error: Allocator.Error || Fat32Self.Error
/// Allocator.Error - Error allocating memory.
/// Fat32Self.Error - The name provided cannot be converted to a valid FAT32 name.
///
fn createEntries(allocator: Allocator, name: []const u8, cluster: u32, attributes: ShortName.Attributes, existing_short_names: []const [11]u8) (Allocator.Error || Fat32Self.Error)!FatDirEntry {
const long_name = try nameToLongName(allocator, name);
defer allocator.free(long_name);
const short_name = try longNameToShortName(long_name, existing_short_names);
const short_entry = createShortNameEntry(short_name, attributes, cluster);
const long_entry = try createLongNameEntry(allocator, long_name, short_entry.calcCheckSum());
return FatDirEntry{
.long_entry = long_entry,
.short_entry = short_entry,
};
}
///
/// Helper for converting a raw file name to a valid FAT32 long file name. The returned
/// name will need to be freed by the allocator.
///
/// Arguments:
/// IN allocator: Allocator - The allocator for creating the FAT32 long file name.
/// IN name: []const u8 - The raw file name.
///
/// Return: []const u16
/// A valid UTF-16 FAT32 long file name.
///
/// Error: Allocator.Error || Fat32Self.Error
/// Allocator.Error - Error allocating memory.
/// Fat32Self.Error.InvalidName - The file name cannot be converted to a valid long name.
///
fn nameToLongName(allocator: Allocator, name: []const u8) (Allocator.Error || Fat32Self.Error)![]const u16 {
// Allocate a buffer to translate to UFT16. Then length of the UFT8 will be more than enough
// TODO: Calc the total length and use appendAssumeCapacity
var utf16_buff = try ArrayList(u16).initCapacity(allocator, name.len);
defer utf16_buff.deinit();
// The name is in UTF8, this needs to be conversed to UTF16
// This also checks for valid UTF8 characters
const utf8_view = std.unicode.Utf8View.init(name) catch return Fat32Self.Error.InvalidName;
var utf8_it = utf8_view.iterator();
// Make sure the code points as valid for the long name
var ignored_leading = false;
while (utf8_it.nextCodepoint()) |code_point| {
// Ignore leading spaces
if (!ignored_leading and code_point == ' ') {
continue;
}
ignored_leading = true;
// If it is larger than 0xFFFF, then it cannot fit in UTF16 so invalid.
// Can't have control characters (including the DEL key)
if (code_point > 0xFFFF or code_point < 0x20 or code_point == 0x7F) {
return Fat32Self.Error.InvalidName;
}
// Check for invalid characters
const invalid_chars = "\"*/:<>?\\|";
inline for (invalid_chars) |char| {
if (char == code_point) {
return Fat32Self.Error.InvalidName;
}
}
// Valid character
try utf16_buff.append(@intCast(u16, code_point));
}
// Remove trailing spaces and dots
// And return the name
const long_name = std.mem.trimRight(u16, utf16_buff.toOwnedSlice(), &[_]u16{ ' ', '.' });
errdefer allocator.free(long_name);
// Check the generated name is a valid length
if (long_name.len > 255) {
return Fat32Self.Error.InvalidName;
}
return long_name;
}
///
/// Helper function for checking if a u16 long name character can be converted to a valid
/// OEM u8 character
///
/// Arguments:
/// IN char: u16 - The character to check
///
/// Return: ?u8
/// The successful converted character or null if is an invalid OEM u8 char.
///
fn isValidSFNChar(char: u16) ?u8 {
// Ignore spaces
if (char == 0x20) {
return null;
}
// If not a u8 char or CP437 char, then replace with a _
if (char > 0x7F) {
return CodePage.toCodePage(.CP437, char) catch {
return null;
};
}
// Check for invalid characters, then replace with a _
const invalid_chars = "+,;=[]";
inline for (invalid_chars) |c| {
if (c == char) {
return null;
}
}
return @intCast(u8, char);
}
///
/// Helper function for converting a valid long name to a short file name. This expects
/// valid long name, else is undefined for invalid long names. This also checks against
/// existing short names so there are no clashed within the same directory (appending
/// ~n to the end of the short name if there is a clash).
///
/// Arguments:
/// IN long_name: []const u16 - The long name to convert.
/// IN existing_names: []const [11]u8 - The list of existing short names.
///
/// Return: [11]u8
/// The converted short name.
///
/// Error: Fat32Self.Error
/// Fat32Self.Error.InvalidName - If the directory is fill of the same short file name.
///
fn longNameToShortName(long_name: []const u16, existing_names: []const [11]u8) Fat32Self.Error![11]u8 {
// Pad with spaces
var sfn: [11]u8 = [_]u8{' '} ** 11;
var sfn_i: u8 = 0;
var is_lossy = false;
// TODO: Need to convert to upper case first but don't have proper unicode support for this yet
// Remove leading dots and spaces
var long_name_start: u32 = 0;
for (long_name) |char| {
if (char != '.') {
break;
}
// If there is, then it is lossy
long_name_start += 1;
is_lossy = true;
}
// Get the last dot in the string
const last_dot_index = std.mem.lastIndexOf(u16, long_name[long_name_start..], &[_]u16{'.'});
for (long_name[long_name_start..]) |char, i| {
// Break when we reach the max of the short name or the last dot
if (char == '.') {
if (last_dot_index) |index| {
if (i == index) {
break;
}
}
// Else ignore it, and is lossy
is_lossy = true;
continue;
}
if (sfn_i == 8) {
is_lossy = true;
break;
}
if (isValidSFNChar(char)) |oem_char| {
// Valid SFN char, and convert to upper case
// TODO: Need proper unicode uppercase
sfn[sfn_i] = std.ascii.toUpper(oem_char);
sfn_i += 1;
} else {
// Spaces don't need to be replaced, just ignored, but still set the lossy
if (char != 0x20) {
sfn[sfn_i] = '_';
sfn_i += 1;
}
is_lossy = true;
}
}
// Save the name index
const name_index = sfn_i;
// Go to the last dot, if there isn't one, return what we have
const index = (last_dot_index orelse return sfn) + long_name_start;
sfn_i = 8;
// +1 as the index will be on the DOT and we don't need to include it
for (long_name[index + 1 ..]) |char| {
// Break when we reach the max of the short name
if (sfn_i == 11) {
is_lossy = true;
break;
}
if (isValidSFNChar(char)) |oem_char| {
// Valid SFN char, and convert to upper case
// TODO: Need proper unicode uppercase
sfn[sfn_i] = std.ascii.toUpper(oem_char);
sfn_i += 1;
} else {
// Spaces don't need to be replaced, just ignored, but still set the lossy
if (char != 0x20) {
sfn[sfn_i] = '_';
sfn_i += 1;
}
is_lossy = true;
}
}
// 0xE5 is used for a deleted file, but is a valid UTF8 character, so use 0x05 instead
if (sfn[0] == 0xE5) {
sfn[0] = 0x05;
}
// Is there a collision of file names
// Find n in ~n in the existing files
var trail_number: u32 = 0;
var full_name_match = false;
for (existing_names) |existing_name| {
// Only need to check the 8 char file name, not extension
var i: u8 = 0;
while (i < 8) : (i += 1) {
if (existing_name[i] != sfn[i] and existing_name[i] == '~') {
// Read the number and break
// -3 as we exclude the extension
// +1 as we are at the '~'
i += 1;
const end_num = std.mem.indexOf(u8, existing_name[0..], " ") orelse existing_name.len - 3;
const num = std.fmt.parseInt(u32, existing_name[i..end_num], 10) catch {
break;
};
if (num > trail_number) {
trail_number = num;
}
break;
}
// Not the same file name
if (existing_name[i] != sfn[i] and (!is_lossy or existing_name[i] != '~')) {
break;
}
}
// If match the full name, then need to add trail
if (i == 8) {
full_name_match = true;
}
}
// If there were some losses, then wee need to add a number to the end
if (is_lossy or full_name_match) {
// Check if we have the max file names
if (trail_number == 999999) {
return Error.InvalidName;
}
// Increase the trail number as this will be the number to append
trail_number += 1;
// Format this as a string, can't be more than 6 characters
var trail_number_str: [6]u8 = undefined;
const trail_number_str_end = std.fmt.formatIntBuf(trail_number_str[0..], trail_number, 10, .lower, .{});
// Get the index to put the ~n
var number_trail_index = if (name_index > 7 - trail_number_str_end) 7 - trail_number_str_end else name_index;
sfn[number_trail_index] = '~';
for (trail_number_str[0..trail_number_str_end]) |num_str| {
number_trail_index += 1;
sfn[number_trail_index] = num_str;
}
}
return sfn;
}
///
/// Helper function for creating the long name dir entries from the long name. The return
/// array will need to be freed by the caller. This expects a valid long name else undefined
/// behavior.
///
/// Arguments:
/// IN allocator: Allocator - The allocator for the long name array
/// IN long_name: []const u16 - The valid long name.
/// IN check_sum: u8 - The short name check sum for the long entry.
///
/// Return: []LongName
/// The list of long name entries read to be written to disk.
///
/// Error: Allocator.Error
/// Allocator.Error - Error allocating memory for the long name entries.
///
fn createLongNameEntry(allocator: Allocator, long_name: []const u16, check_sum: u8) Allocator.Error![]LongName {
// Calculate the number of long entries (round up). LFN are each 13 characters long
const num_lfn_entries = @intCast(u8, (long_name.len + 12) / 13);
// Create the long entries
var lfn_array = try allocator.alloc(LongName, num_lfn_entries);
errdefer allocator.free(lfn_array);
// Work backwards because it is easier
var backwards_index = num_lfn_entries;
while (backwards_index > 0) : (backwards_index -= 1) {
// If this is the first entry, then the first bytes starts with 0x40
const entry_index = num_lfn_entries - backwards_index;
const order = if (backwards_index == 1) 0x40 | num_lfn_entries else entry_index + 1;
// Get the working slice of 13 characters
// NULL terminate and pad with 0xFFFF if less than 13 characters
const working_name: [13]u16 = blk: {
var temp: [13]u16 = [_]u16{0xFFFF} ** 13;
const long_name_slice = long_name[(entry_index * 13)..];
if (long_name_slice.len < 13) {
for (long_name_slice) |char, i| {
temp[i] = char;
}
// NULL terminated
temp[long_name_slice.len] = 0x0000;
} else {
for (temp) |*char, i| {
char.* = long_name[(entry_index * 13) + i];
}
}
break :blk temp;
};
// Create the entry
lfn_array[backwards_index - 1] = .{
.order = order,
.first = working_name[0..5].*,
.check_sum = check_sum,
.second = working_name[5..11].*,
.third = working_name[11..13].*,
};
}
return lfn_array;
}
///
/// Helper function fro creating a short name entry. This calls the system time to get the
/// current date and time for the new file/directory. This assumes a valid short name else
/// undefined behavior.
///
/// Arguments:
/// IN name: [11]u8 - The short name.
/// IN attributes: ShortName.Attributes - The attribute for the short name entry.
/// IN cluster: u32 - The cluster where this will point to.
///
/// Return: ShortName
/// The short name entry with the current time used.
///
fn createShortNameEntry(name: [11]u8, attributes: ShortName.Attributes, cluster: u32) ShortName {
const date_time = arch.getDateTime();
const date = @intCast(u16, date_time.day | date_time.month << 5 | (date_time.year - 1980) << 9);
const time = @intCast(u16, date_time.second / 2 | date_time.minute << 5 | date_time.hour << 11);
return .{
.name = name[0..8].*,
.extension = name[8..11].*,
.attributes = @enumToInt(attributes),
.time_created_tenth = @intCast(u8, (date_time.second % 2) * 100),
.time_created = time,
.date_created = date,
.date_last_access = date,
.cluster_high = @truncate(u16, cluster >> 16),
.time_last_modification = time,
.date_last_modification = date,
.cluster_low = @truncate(u16, cluster),
.size = 0x00000000,
};
}
///
/// Helper function for writing a new file/folder entry. This will create the new entry
/// under the provided cluster. If the cluster is full or not big enough the FAT will
/// be extended and a new cluster will be allocated. This expects valid entries. Inputs
/// are assumed to be correct.
///
/// Arguments:
/// IN self: *Fat32Self - Self for the current instance of the FAT32 filesystem.
/// IN entries: FatDirEntry - The new entries to be written.
/// IN at_cluster: u32 - The cluster to write the entries to.
/// IN next_free_cluster_hint: u32 - The next free cluster to be used as a hint to find
/// new clusters for large entries.
/// IN initial_cluster_offset: u32 - The initial offset into the cluster to write to.
///
/// Return: struct{cluster: u32, offset: u32}
/// cluster - The cluster at which the short entry is located
/// offset - The offset at which the short entry is located with in the cluster.
///
/// Error: Allocator.Error || WriteError || ReadError || SeekError
/// Allocator.Error - Error allocating memory.
/// WriteError - Error writing to the underlying stream.
/// ReadError - Error reading the underlying stream.
/// SeekError - Error seeking the underlying stream.
/// Fat32Self.Error - This will relate to allocating a new cluster.
///
fn writeEntries(self: *Fat32Self, entries: FatDirEntry, at_cluster: u32, next_free_cluster_hint: u32, initial_cluster_offset: u32) (Allocator.Error || WriteError || ReadError || SeekError || Fat32Self.Error)!struct { cluster: u32, offset: u32 } {
// Each entry is 32 bytes short + 32 * long len
const entries_size_bytes = 32 + (32 * entries.long_entry.len);
std.debug.assert(at_cluster >= 2);
// Largest possible entry length
std.debug.assert(entries_size_bytes <= 32 + (32 * 20));
// Entries are 32 bytes long, so the offset will need to be aligned to 32 bytes
std.debug.assert(initial_cluster_offset % 32 == 0);
const cluster_size = self.fat_config.bytes_per_sector * self.fat_config.sectors_per_cluster;
// Check free entry
var index = initial_cluster_offset;
// The cluster to write to, this can update as if the cluster provided is full, will need to write to the next free cluster
var write_cluster = at_cluster;
// At the end of the cluster chain, need to alloc a cluster
// Overwrite the at_cluster to use the new one
if (index == cluster_size) {
write_cluster = try self.findNextFreeCluster(next_free_cluster_hint, write_cluster);
index = 0;
}
// TODO: Once FatDirEntry can be a packed struct, then can write as bytes and not convert
var write_buff = try self.allocator.alloc(u8, entries_size_bytes);
defer self.allocator.free(write_buff);
for (entries.long_entry) |long_entry, i| {
initBytes(LongName, long_entry, write_buff[(32 * i)..]);
}
initBytes(ShortName, entries.short_entry, write_buff[write_buff.len - 32 ..]);
// Fill the cluster with the entry
var cluster_offset = index;
var write_index: u32 = 0;
var write_next_index = std.math.min(cluster_size - cluster_offset, write_buff.len);
while (write_index < write_buff.len) : ({
cluster_offset = 0;
write_index = write_next_index;
write_next_index = std.math.min(write_next_index + cluster_size, write_buff.len);
if (write_index < write_buff.len) {
write_cluster = try self.findNextFreeCluster(write_cluster, write_cluster);
}
}) {
const write_sector = self.fat_config.clusterToSector(write_cluster);
try self.stream.seekableStream().seekTo(write_sector * self.fat_config.bytes_per_sector + cluster_offset);
try self.stream.writer().writeAll(write_buff[write_index..write_next_index]);
}
const ret = .{ .cluster = write_cluster, .offset = (index + write_buff.len - 32) % cluster_size };
return ret;
}
///
/// 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) Fat32Self.Error!void {
// Make sure we have closed all files
if (self.opened_files.count() != 0) {
return Fat32Self.Error.FilesStillOpen;
}
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);
}
///
/// Create a test FAT32 filesystem. This will use mkfat32 to create the temporary FAT32 then the
/// stream and fat_config will be replaced by the provided ones. Returned will need to be deinit().
/// This will also set the VFS root node so can use the VFS interfaces without manual setup.
///
/// Arguments:
/// IN allocator: Allocator - The allocator to create the FAT32FS
/// IN stream: anytype - The stream to replace the generated one. This will need to be a
/// fixed buffer stream.
/// IN fat_config: FATConfig - The config to replace the generated one.
///
/// Return: *Fat32FS(@TypeOf(stream))
/// Teh test FAT32 filesystem
///
/// Error: anyerror
/// Any errors. As this is a test function, it doesn't matter what error is returned, if one
/// does, it fails the test.
///
fn testFAT32FS(allocator: Allocator, stream: anytype, fat_config: FATConfig) anyerror!*Fat32FS(@TypeOf(stream)) {
var test_file_buf = try std.testing.allocator.alloc(u8, 35 * 512);
defer allocator.free(test_file_buf);
var temp_stream = &std.io.fixedBufferStream(test_file_buf[0..]);
try mkfat32.Fat32.make(.{ .image_size = test_file_buf.len }, temp_stream, true);
var test_fs = try initialiseFAT32(allocator, temp_stream);
test_fs.stream = stream;
test_fs.fat_config = fat_config;
try vfs.setRoot(test_fs.root_node.node);
return test_fs;
}
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..]);
try 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..]);
try 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;
try 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;
try 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;
try 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..]);
try 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..]);
try 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..]);
try 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..]);
try 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..]);
try 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..]);
try 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..]);
try 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..]);
try 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..]);
try 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..]);
try 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 ";
try 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,
};
try 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,
};
try 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,
};
try 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,
};
try expectEqual(sfn.calcCheckSum(), 0x7A);
}
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, 0x0FFFFFFF, 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
try expectEqual(it.cluster, 2);
try expectEqual(it.cluster_offset, 0);
try expectEqual(it.table_offset, 0);
try 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, 0x0FFFFFFF };
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
try expectEqual(it.cluster, 3);
try expectEqual(it.cluster_offset, 0);
try expectEqual(it.table_offset, 0);
try 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, 0x0F,
// Second 4 FAT. This is where it will seek to
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
};
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, 0x0FFFFFFF };
var expected_fat = [_]u32{ 0x0FFFFFFF, 0x0FFFFFFF, 0x0FFFFFFF, 0x0FFFFFFF };
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
try expectEqual(it.cluster, 4);
try expectEqual(it.cluster_offset, 0);
try expectEqual(it.table_offset, 1);
try 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{});
try 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..]);
try 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..]);
try 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;
try 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, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// Backup FAT region
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// 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, 0x0FFFFFFF, 0x0FFFFFFF };
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..]);
try expectEqual(read, 16);
try expectEqualSlices(u8, buff[0..], "abcd1234ABCD!\"$%");
try expectEqual(it.table_offset, 0);
try expectEqual(it.cluster_offset, 0);
try expectEqual(it.cluster, 0x0FFFFFFF);
}
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{});
try 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, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// Backup FAT region
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// 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);
try 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, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// Backup FAT region
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// 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 'try expectEqual(read, 16);' will fail
const read = (try it.read(buff[0..])) orelse 0;
try expectEqual(read, 16);
try expectEqualSlices(u8, buff[0..], "abcd1234ABCD!\"$%");
try expectEqual(it.table_offset, 0);
try expectEqual(it.cluster_offset, 0);
try expectEqual(it.cluster, 0x0FFFFFFF);
const expect_null = try it.read(buff[read..]);
try 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, 0x0F,
// Backup FAT region
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// 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,
};
try expectEqualSlices(u8, it.cluster_block, "abcd1234ABCD!\"$%");
try it.checkRead();
// nothing changed
try 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, 0x0F,
// Backup FAT region
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x0F,
// 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,
};
try expectEqualSlices(u8, it.cluster_block, "abcd1234ABCD!\"$%");
try it.checkRead();
try expectEqualSlices(u8, it.cluster_block, "efgh5678EFGH^&*(");
try 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, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// Backup FAT region
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// 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,
};
try expectEqualSlices(u8, it.cluster_block, "abcd1234ABCD!\"$%");
try 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, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// Backup FAT region
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// 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();
try 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, 0x0F,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// Backup FAT region
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x0F,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// 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();
try 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, 0x0F,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// Backup FAT region
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x0F,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// 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();
try expectEqualSlices(u8, actual.short_name.getSFNName()[0..], "BSHORT TXT");
try 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, 0x0F,
0x05, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x0F,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// Backup FAT region
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x0F,
0x05, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x0F,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// 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, 0x0F,
// 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, 0x0F,
// 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, 0x0F,
};
// 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,
};
try 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,
};
try 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, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// Backup FAT region
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// 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, 0x0F,
// 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,
};
try 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, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// Backup FAT region
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// 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, 0x0F,
// 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,
};
try 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, 0x0F,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// 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, 0x0F,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// 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, 0x0F,
// 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();
try expectEqualSlices(u8, actual.short_name.getSFNName()[0..], "LOOOOO~1TXT");
try 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, 0x0F,
// 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, 0x0F,
// 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, 0x0F,
// 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, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x0F,
// 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();
try expectEqualSlices(u8, actual1.short_name.getSFNName()[0..], "LOOOOO~1TXT");
try expectEqual(actual1.long_name, null);
const actual2 = (try it.next()) orelse return error.TestFail;
defer actual2.deinit();
try expectEqualSlices(u8, actual2.short_name.getSFNName()[0..], "RAMDIS~1TXT");
try expectEqualSlices(u8, actual2.long_name.?, "ramdisk_test1.txt");
try 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, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// Backup FAT region
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// 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);
try 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, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// Backup FAT region
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// Data region (too short)
'a', 'b', 'c', 'd', '1', '2', '3', '4',
};
var stream = &std.io.fixedBufferStream(buff_stream[0..]);
try expectError(error.BadRead, Fat32FS(@TypeOf(stream)).EntryIterator.init(std.testing.allocator, fat_config, 2, stream));
}
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() catch unreachable;
try expectEqual(test_fs.fs.getRootNode(test_fs.fs), &test_fs.root_node.node.Dir);
try expectEqual(test_fs.root_node.cluster, 2);
try expectEqual(test_fs.fat_config.root_directory_cluster, 2);
}
test "Fat32FS.createNode - 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() catch unreachable;
const dir_node = try test_fs.createNode(3, 0, 0, 0, .CREATE_DIR);
defer std.testing.allocator.destroy(dir_node);
try expect(dir_node.isDir());
try expect(test_fs.opened_files.contains(dir_node));
const opened_info = test_fs.opened_files.fetchRemove(dir_node).?.value;
defer std.testing.allocator.destroy(opened_info);
try expectEqual(opened_info.cluster, 3);
try expectEqual(opened_info.size, 0);
}
test "Fat32FS.createNode - 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() catch unreachable;
const file_node = try test_fs.createNode(4, 16, 0, 0, .CREATE_FILE);
defer std.testing.allocator.destroy(file_node);
try expect(file_node.isFile());
try expect(test_fs.opened_files.contains(file_node));
const opened_info = test_fs.opened_files.fetchRemove(file_node).?.value;
defer std.testing.allocator.destroy(opened_info);
try expectEqual(opened_info.cluster, 4);
try expectEqual(opened_info.size, 16);
}
test "Fat32FS.createNode - symlink" {
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() catch unreachable;
try expectError(error.InvalidFlags, test_fs.createNode(4, 16, 0, 0, .CREATE_SYMLINK));
}
test "Fat32FS.createNode - no create" {
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() catch unreachable;
try expectError(error.InvalidFlags, test_fs.createNode(4, 16, 0, 0, .NO_CREATION));
}
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() catch unreachable;
// 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;
try expectError(error.OutOfMemory, test_fs.createNode(3, 16, 0, 0, .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() catch unreachable;
var test_node_1 = try test_fs.createNode(3, 16, 0, 0, .CREATE_FILE);
defer test_node_1.File.close();
const actual = try test_fs.getDirCluster(&test_fs.root_node.node.Dir);
try 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() catch unreachable;
var test_node_1 = try test_fs.createNode(5, 0, 0, 0, .CREATE_DIR);
defer test_node_1.Dir.close();
const actual = try test_fs.getDirCluster(&test_node_1.Dir);
try 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() catch unreachable;
var test_node_1 = try test_fs.createNode(5, 0, 0, 0, .CREATE_DIR);
const elem = test_fs.opened_files.fetchRemove(test_node_1).?.value;
std.testing.allocator.destroy(elem);
try 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() catch unreachable;
var test_node_1 = try test_fs.createNode(5, 0, 0, 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;
try 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() catch unreachable;
var fa = std.testing.FailingAllocator.init(std.testing.allocator, 2);
const allocator = fa.allocator();
test_fs.allocator = allocator;
try 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() catch unreachable;
var fa = std.testing.FailingAllocator.init(std.testing.allocator, 3);
const allocator = fa.allocator();
test_fs.allocator = allocator;
try 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() catch unreachable;
const file_node = try Fat32FS(@TypeOf(test_fat32_image)).openImpl(test_fs.fs, &test_fs.root_node.node.Dir, "short.txt");
defer file_node.File.close();
}
test "Fat32FS.openImpl - match long name" {
return error.SkipZigTest;
//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() catch unreachable;
//
//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");
}
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() catch unreachable;
var test_node_1 = try test_fs.createNode(5, 0, 0, 0, .CREATE_DIR);
defer test_node_1.Dir.close();
try 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(.{ .image_size = test_file_buf.len }, stream, true);
var test_fs = try initialiseFAT32(std.testing.allocator, stream);
defer test_fs.destroy() catch unreachable;
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, 0x0F,
// 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, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x0F,
// 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..]);
try vfs.setRoot(test_fs.root_node.node);
const file = try vfs.openFile("/ramdisk_test1.txt", .NO_CREATION);
defer file.close();
try expect(test_fs.opened_files.contains(@ptrCast(*const vfs.Node, file)));
const opened_info = test_fs.opened_files.get(@ptrCast(*const vfs.Node, file)).?;
try expectEqual(opened_info.cluster, 3);
try expectEqual(opened_info.size, 16);
}
fn testOpenRec(dir_node: *const vfs.DirNode, path: []const u8) anyerror!void {
var test_files = try std.fs.cwd().openDir(path, .{ .iterate = true });
defer test_files.close();
var it = test_files.iterate();
while (try it.next()) |file| {
if (file.kind == .Directory) {
var dir_path = try std.testing.allocator.alloc(u8, path.len + file.name.len + 1);
defer std.testing.allocator.free(dir_path);
std.mem.copy(u8, dir_path[0..], path);
dir_path[path.len] = '/';
std.mem.copy(u8, dir_path[path.len + 1 ..], file.name);
const new_dir = &(try dir_node.open(file.name, .NO_CREATION, .{})).Dir;
defer new_dir.close();
try testOpenRec(new_dir, dir_path);
} else {
const open_file = &(try dir_node.open(file.name, .NO_CREATION, .{})).File;
defer open_file.close();
}
}
}
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() catch unreachable;
try vfs.setRoot(test_fs.root_node.node);
try testOpenRec(&test_fs.root_node.node.Dir, "test/fat32/test_files");
}
test "Fat32FS.open - create file" {
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(.{ .image_size = test_file_buf.len }, stream, true);
var test_fs = try initialiseFAT32(std.testing.allocator, stream);
defer test_fs.destroy() catch unreachable;
try vfs.setRoot(test_fs.root_node.node);
// Open and close
const open_file = try vfs.openFile("/fileαfile€file.txt", .CREATE_FILE);
open_file.close();
// Can't open it as a dir
try expectError(error.IsAFile, vfs.openDir("/fileαfile€file.txt", .NO_CREATION));
// Can we open the same file
const read_file = try vfs.openFile("/fileαfile€file.txt", .NO_CREATION);
defer read_file.close();
// Reads nothing
var buff = [_]u8{0xAA} ** 512;
const read = read_file.read(buff[0..]);
try expectEqual(read, 0);
try expectEqualSlices(u8, buff[0..], &[_]u8{0xAA} ** 512);
}
test "Fat32FS.open - create directory" {
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(.{ .image_size = test_file_buf.len }, stream, true);
var test_fs = try initialiseFAT32(std.testing.allocator, stream);
defer test_fs.destroy() catch unreachable;
try vfs.setRoot(test_fs.root_node.node);
// Open and close
const open_dir = try vfs.openDir("/fileαfile€file", .CREATE_DIR);
open_dir.close();
// Can't open it as a file
try expectError(error.IsADirectory, vfs.openFile("/fileαfile€file", .NO_CREATION));
const open = try vfs.openDir("/fileαfile€file", .NO_CREATION);
defer open.close();
}
test "Fat32FS.open - create symlink" {
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(.{ .image_size = test_file_buf.len }, stream, true);
var test_fs = try initialiseFAT32(std.testing.allocator, stream);
defer test_fs.destroy() catch unreachable;
try vfs.setRoot(test_fs.root_node.node);
try expectError(error.InvalidFlags, vfs.openSymlink("/fileαfile€file.txt", "/file.txt", .CREATE_SYMLINK));
}
test "Fat32FS.open - create nested directories" {
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(.{ .image_size = test_file_buf.len }, stream, true);
var test_fs = try initialiseFAT32(std.testing.allocator, stream);
defer test_fs.destroy() catch unreachable;
try vfs.setRoot(test_fs.root_node.node);
const open1 = try vfs.openDir("/fileαfile€file", .CREATE_DIR);
defer open1.close();
const open2 = try vfs.openDir("/fileαfile€file/folder", .CREATE_DIR);
defer open2.close();
const open3 = try vfs.openDir("/fileαfile€file/folder/1", .CREATE_DIR);
defer open3.close();
const open4 = try vfs.openDir("/fileαfile€file/folder/1/2", .CREATE_DIR);
defer open4.close();
const open5 = try vfs.openDir("/fileαfile€file/folder/1/2/3", .CREATE_DIR);
defer open5.close();
const open6 = try vfs.openDir("/fileαfile€file/folder/1/2/3/end", .CREATE_DIR);
defer open6.close();
const open_dir = try vfs.openDir("/fileαfile€file/folder/1/2/3/end", .NO_CREATION);
defer open_dir.close();
}
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() catch unreachable;
// Craft a node
var node = try std.testing.allocator.create(vfs.Node);
defer std.testing.allocator.destroy(node);
node.* = .{ .File = .{ .fs = test_fs.fs } };
try 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() catch unreachable;
var test_node = try test_fs.createNode(5, 16, 0, 0, .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;
try 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() catch unreachable;
try 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..]);
try 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() catch unreachable;
try 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..]);
try expectEqualSlices(u8, buff[0..read], "short.txt");
// The rest should be unchanged
try 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() catch unreachable;
try vfs.setRoot(test_fs.root_node.node);
const test_node = try vfs.openFile("/large_file.txt", .NO_CREATION);
defer test_node.close();
var buff = [_]u8{0xAA} ** 8450;
const read = try test_node.read(buff[0..]);
try expectEqual(read, 8450);
const large_file_content = @embedFile("../../../test/fat32/test_files/large_file.txt");
try expectEqualSlices(u8, buff[0..], large_file_content[0..]);
}
fn testReadRec(dir_node: *const vfs.DirNode, path: []const u8, read_big: bool) anyerror!void {
var test_files = try std.fs.cwd().openDir(path, .{ .iterate = true });
defer test_files.close();
var it = test_files.iterate();
while (try it.next()) |file| {
if (file.kind == .Directory) {
var dir_path = try std.testing.allocator.alloc(u8, path.len + file.name.len + 1);
defer std.testing.allocator.free(dir_path);
std.mem.copy(u8, dir_path[0..], path);
dir_path[path.len] = '/';
std.mem.copy(u8, dir_path[path.len + 1 ..], file.name);
const new_dir = &(try dir_node.open(file.name, .NO_CREATION, .{})).Dir;
defer new_dir.close();
try testReadRec(new_dir, dir_path, read_big);
} else {
const open_file = &(try dir_node.open(file.name, .NO_CREATION, .{})).File;
defer open_file.close();
// Have tested the large file
if (!read_big and std.mem.eql(u8, file.name, "large_file.txt")) {
continue;
} else if (read_big and std.mem.eql(u8, file.name, "large_file.txt")) {
var buff = [_]u8{0xAA} ** 8450;
const large_file_content = @embedFile("../../../test/fat32/test_files/large_file.txt");
const read = try open_file.read(buff[0..]);
try expectEqualSlices(u8, buff[0..], large_file_content[0..]);
try expectEqual(read, 8450);
continue;
}
// 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
try expectEqual(file.name.len, read);
try expectEqualSlices(u8, buff[0..read], file.name[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() catch unreachable;
try 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();
try testReadRec(&test_fs.root_node.node.Dir, "test/fat32/test_files", false);
}
test "Fat32FS.findNextFreeCluster - free on error" {
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 = false,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = 0x0FFFFFFF,
};
// Too small
var fat_buff_stream = [_]u8{
// FAT region 1
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x0F,
};
var stream = &std.io.fixedBufferStream(fat_buff_stream[0..]);
var test_fs = try testFAT32FS(std.testing.allocator, stream, fat_config);
defer test_fs.destroy() catch unreachable;
try expectError(error.BadRead, test_fs.findNextFreeCluster(2, null));
}
test "Fat32FS.findNextFreeCluster - alloc cluster in first sector" {
const fat_config = FATConfig{
.bytes_per_sector = 32,
.sectors_per_cluster = 1,
.reserved_sectors = 0,
.hidden_sectors = undefined,
.total_sectors = undefined,
.sectors_per_fat = 2,
.root_directory_cluster = undefined,
.fsinfo_sector = undefined,
.backup_boot_sector = undefined,
.has_fs_info = false,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = 0x0FFFFFFF,
};
// 6th entry is free
var fat_buff_stream = [_]u8{
// FAT region 1
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x0F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// FAT region 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,
// Backup FAT region 1
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x0F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Backup FAT region 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(fat_buff_stream[0..]);
var test_fs = try testFAT32FS(std.testing.allocator, stream, fat_config);
defer test_fs.destroy() catch unreachable;
const cluster = try test_fs.findNextFreeCluster(2, null);
try expectEqual(cluster, 6);
// check the FAT where the update would happen + backup FAT
try expectEqualSlices(u8, fat_buff_stream[24..28], &[_]u8{ 0xFF, 0xFF, 0xFF, 0x0F });
try expectEqualSlices(u8, fat_buff_stream[88..92], &[_]u8{ 0xFF, 0xFF, 0xFF, 0x0F });
}
test "Fat32FS.findNextFreeCluster - alloc cluster in second sector" {
const fat_config = FATConfig{
.bytes_per_sector = 32,
.sectors_per_cluster = 1,
.reserved_sectors = 0,
.hidden_sectors = undefined,
.total_sectors = undefined,
.sectors_per_fat = 2,
.root_directory_cluster = undefined,
.fsinfo_sector = undefined,
.backup_boot_sector = undefined,
.has_fs_info = false,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = 0x0FFFFFFF,
};
// 6th entry is free
var fat_buff_stream = [_]u8{
// FAT region 1
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x0F,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// FAT region 2
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Backup FAT region 1
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x0F,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// Backup FAT region 2
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
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(fat_buff_stream[0..]);
var test_fs = try testFAT32FS(std.testing.allocator, stream, fat_config);
defer test_fs.destroy() catch unreachable;
const cluster = try test_fs.findNextFreeCluster(10, null);
try expectEqual(cluster, 10);
// check the FAT where the update would happen + backup FAT
try expectEqualSlices(u8, fat_buff_stream[40..44], &[_]u8{ 0xFF, 0xFF, 0xFF, 0x0F });
try expectEqualSlices(u8, fat_buff_stream[104..108], &[_]u8{ 0xFF, 0xFF, 0xFF, 0x0F });
}
test "Fat32FS.findNextFreeCluster - alloc cluster over sector boundary" {
const fat_config = FATConfig{
.bytes_per_sector = 32,
.sectors_per_cluster = 1,
.reserved_sectors = 0,
.hidden_sectors = undefined,
.total_sectors = undefined,
.sectors_per_fat = 2,
.root_directory_cluster = undefined,
.fsinfo_sector = undefined,
.backup_boot_sector = undefined,
.has_fs_info = false,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = 0x0FFFFFFF,
};
// 6th entry is free
var fat_buff_stream = [_]u8{
// FAT region 1
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x0F,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// FAT region 2
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Backup FAT region 1
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x0F,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// Backup FAT region 2
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
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(fat_buff_stream[0..]);
var test_fs = try testFAT32FS(std.testing.allocator, stream, fat_config);
defer test_fs.destroy() catch unreachable;
const cluster = try test_fs.findNextFreeCluster(2, null);
try expectEqual(cluster, 10);
// check the FAT where the update would happen + backup FAT
try expectEqualSlices(u8, fat_buff_stream[24..28], &[_]u8{ 0xFF, 0xFF, 0xFF, 0x0F });
try expectEqualSlices(u8, fat_buff_stream[88..92], &[_]u8{ 0xFF, 0xFF, 0xFF, 0x0F });
}
test "Fat32FS.findNextFreeCluster - no free cluster" {
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 = false,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = 0x0FFFFFFF,
};
// 6th entry is free
var fat_buff_stream = [_]u8{
// FAT region 1
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x0F,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
// FAT region 2
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
};
var stream = &std.io.fixedBufferStream(fat_buff_stream[0..]);
var test_fs = try testFAT32FS(std.testing.allocator, stream, fat_config);
defer test_fs.destroy() catch unreachable;
try expectError(error.DiskFull, test_fs.findNextFreeCluster(2, null));
}
test "Fat32FS.findNextFreeCluster - updates FSInfo" {
const fat_config = FATConfig{
.bytes_per_sector = 512,
.sectors_per_cluster = 1,
.reserved_sectors = 2,
.hidden_sectors = undefined,
.total_sectors = undefined,
.sectors_per_fat = 2,
.root_directory_cluster = undefined,
.fsinfo_sector = 0,
.backup_boot_sector = 1,
.has_fs_info = true,
.number_free_clusters = 10,
.next_free_cluster = 6,
.cluster_end_marker = 0x0FFFFFFF,
};
// 6th entry is free
var buff_stream = [_]u8{0x00} ** 488 ++ [_]u8{
// FSInfo
0x0A, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
} ++ [_]u8{0x00} ** 504 ++ [_]u8{
// Backup FSInfo
0x0A, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
} ++ [_]u8{
// FAT region 1
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x0F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
} ++ [_]u8{0x00} ** 480 ++ [_]u8{
// FAT region 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,
} ++ [_]u8{0x00} ** 480 ++ [_]u8{
// Backup FAT region 1
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x0F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
} ++ [_]u8{0x00} ** 480 ++ [_]u8{
// Backup FAT region 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,
} ++ [_]u8{0x00} ** 480;
var stream = &std.io.fixedBufferStream(buff_stream[0..]);
var test_fs = try testFAT32FS(std.testing.allocator, stream, fat_config);
defer test_fs.destroy() catch unreachable;
const cluster = try test_fs.findNextFreeCluster(2, null);
try expectEqual(cluster, 6);
try expectEqual(test_fs.fat_config.number_free_clusters, 9);
try expectEqual(test_fs.fat_config.next_free_cluster, 7);
try expectEqual(buff_stream[488], 9);
try expectEqual(buff_stream[492], 7);
try expectEqual(buff_stream[1000], 9);
try expectEqual(buff_stream[1004], 7);
}
test "Fat32FS.findNextFreeCluster - updates cluster chain with parent" {
const fat_config = FATConfig{
.bytes_per_sector = 32,
.sectors_per_cluster = 1,
.reserved_sectors = 0,
.hidden_sectors = undefined,
.total_sectors = undefined,
.sectors_per_fat = 2,
.root_directory_cluster = undefined,
.fsinfo_sector = undefined,
.backup_boot_sector = undefined,
.has_fs_info = false,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = 0x0FFFFFFF,
};
// 6th entry is free
var fat_buff_stream = [_]u8{
// FAT region 1
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x0F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// FAT region 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,
// Backup FAT region 1
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x0F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Backup FAT region 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(fat_buff_stream[0..]);
var test_fs = try testFAT32FS(std.testing.allocator, stream, fat_config);
defer test_fs.destroy() catch unreachable;
const cluster = try test_fs.findNextFreeCluster(2, 5);
try expectEqual(cluster, 6);
// check the FAT where the update would happen + backup FAT
try expectEqualSlices(u8, fat_buff_stream[20..28], &[_]u8{ 0x06, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x0F });
try expectEqualSlices(u8, fat_buff_stream[84..92], &[_]u8{ 0x06, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x0F });
}
test "Fat32FS.nameToLongName - name too long" {
const long_name = [_]u8{'A'} ** 256;
var stream = &std.io.fixedBufferStream(&[_]u8{});
try expectError(error.InvalidName, Fat32FS(@TypeOf(stream)).nameToLongName(std.testing.allocator, long_name[0..]));
}
test "Fat32FS.nameToLongName - leading spaces" {
const name_cases = [_][]const u8{
" file.txt",
" file.txt",
[_]u8{' '} ** 256 ++ "file.txt",
};
var stream = &std.io.fixedBufferStream(&[_]u8{});
const expected = [_]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' };
for (name_cases) |case| {
const actual = try Fat32FS(@TypeOf(stream)).nameToLongName(std.testing.allocator, case[0..]);
defer std.testing.allocator.free(actual);
try expectEqualSlices(u16, expected[0..], actual);
}
}
test "Fat32FS.nameToLongName - invalid name" {
const name_cases = [_][]const u8{
"\"file.txt",
"*file.txt",
"/file.txt",
":file.txt",
"<file.txt",
">file.txt",
"?file.txt",
"\\file.txt",
"|file.txt",
[_]u8{0x10} ++ "file.txt",
[_]u8{0x7F} ++ "file.txt",
"\u{12345}file.txt",
};
var stream = &std.io.fixedBufferStream(&[_]u8{});
for (name_cases) |case| {
try expectError(error.InvalidName, Fat32FS(@TypeOf(stream)).nameToLongName(std.testing.allocator, case[0..]));
}
}
test "Fat32FS.nameToLongName - trailing spaces or dots" {
const name_cases = [_][]const u8{
"file.txt ",
"file.txt....",
"file.txt . .",
"file.txt" ++ [_]u8{' '} ** 256,
};
var stream = &std.io.fixedBufferStream(&[_]u8{});
const expected = [_]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' };
for (name_cases) |case| {
const actual = try Fat32FS(@TypeOf(stream)).nameToLongName(std.testing.allocator, case[0..]);
defer std.testing.allocator.free(actual);
try expectEqualSlices(u16, expected[0..], actual);
}
}
test "Fat32FS.nameToLongName - valid name" {
const name_cases = [_][]const u8{
"....leading_dots.txt",
"[nope].txt",
"A_verY_Long_File_namE_With_normal_Extension.tXt",
"dot.in.file.txt",
"file.long_ext",
"file.t x t",
"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",
"nope.[x]",
"s p a c e s.txt",
"UTF16.€xt",
"UTF16€.txt",
"αlpha.txt",
"file.txt",
};
var stream = &std.io.fixedBufferStream(&[_]u8{});
for (name_cases) |case| {
// Can just test no error
const actual = try Fat32FS(@TypeOf(stream)).nameToLongName(std.testing.allocator, case[0..]);
defer std.testing.allocator.free(actual);
}
}
test "Fat32FS.isValidSFNChar - invalid" {
var stream = &std.io.fixedBufferStream(&[_]u8{});
try expectEqual(Fat32FS(@TypeOf(stream)).isValidSFNChar(' '), null);
try expectEqual(Fat32FS(@TypeOf(stream)).isValidSFNChar('€'), null);
try expectEqual(Fat32FS(@TypeOf(stream)).isValidSFNChar('+'), null);
try expectEqual(Fat32FS(@TypeOf(stream)).isValidSFNChar(','), null);
try expectEqual(Fat32FS(@TypeOf(stream)).isValidSFNChar(';'), null);
try expectEqual(Fat32FS(@TypeOf(stream)).isValidSFNChar('='), null);
try expectEqual(Fat32FS(@TypeOf(stream)).isValidSFNChar('['), null);
try expectEqual(Fat32FS(@TypeOf(stream)).isValidSFNChar(']'), null);
try expectEqual(Fat32FS(@TypeOf(stream)).isValidSFNChar('α'), 0xE0);
try expectEqual(Fat32FS(@TypeOf(stream)).isValidSFNChar('a'), 'a');
}
test "Fat32FS.longNameToShortName - leading dots and spaces" {
// Using valid long names
const name_cases = [_][]const u8{
"....file.txt",
". . file.txt",
};
var stream = &std.io.fixedBufferStream(&[_]u8{});
const expected = "FILE~1 TXT";
for (name_cases) |case| {
const long_name = try Fat32FS(@TypeOf(stream)).nameToLongName(std.testing.allocator, case[0..]);
defer std.testing.allocator.free(long_name);
const actual = try Fat32FS(@TypeOf(stream)).longNameToShortName(long_name, &[_][11]u8{});
try expectEqualSlices(u8, actual[0..], expected[0..]);
}
}
test "Fat32FS.longNameToShortName - embedded spaces" {
// Using valid long names
const name_cases = [_][]const u8{
"f i l e.txt",
"fi le.txt",
"file.t x t",
"file.tx t",
};
var stream = &std.io.fixedBufferStream(&[_]u8{});
const expected = "FILE~1 TXT";
for (name_cases) |case| {
const long_name = try Fat32FS(@TypeOf(stream)).nameToLongName(std.testing.allocator, case[0..]);
defer std.testing.allocator.free(long_name);
const actual = try Fat32FS(@TypeOf(stream)).longNameToShortName(long_name, &[_][11]u8{});
try expectEqualSlices(u8, actual[0..], expected[0..]);
}
}
test "Fat32FS.longNameToShortName - dot before end" {
// Using valid long names
const name_cases = [_][]const u8{
"fi.le.txt",
"f.i.l.e.txt",
"fi.....le.txt",
"fi. le.txt",
};
var stream = &std.io.fixedBufferStream(&[_]u8{});
const expected = "FILE~1 TXT";
for (name_cases) |case| {
const long_name = try Fat32FS(@TypeOf(stream)).nameToLongName(std.testing.allocator, case[0..]);
defer std.testing.allocator.free(long_name);
const actual = try Fat32FS(@TypeOf(stream)).longNameToShortName(long_name, &[_][11]u8{});
try expectEqualSlices(u8, actual[0..], expected[0..]);
}
}
test "Fat32FS.longNameToShortName - long name" {
// Using valid long names
const name_cases = [_][]const u8{
"loooooong.txt",
"loooooo.ng.txt",
"loooooo.ng€.txt",
"looooo€.ng.txt",
"loooooong.txttttt",
"looooo.txttttt",
};
var stream = &std.io.fixedBufferStream(&[_]u8{});
const expected = "LOOOOO~1TXT";
for (name_cases) |case| {
const long_name = try Fat32FS(@TypeOf(stream)).nameToLongName(std.testing.allocator, case[0..]);
defer std.testing.allocator.free(long_name);
const actual = try Fat32FS(@TypeOf(stream)).longNameToShortName(long_name, &[_][11]u8{});
try expectEqualSlices(u8, actual[0..], expected[0..]);
}
}
test "Fat32FS.longNameToShortName - short name" {
// Using valid long names
const name_cases = [_][]const u8{
"file1234.txt",
"FiLe1234.txt",
};
var stream = &std.io.fixedBufferStream(&[_]u8{});
const expected = "FILE1234TXT";
for (name_cases) |case| {
const long_name = try Fat32FS(@TypeOf(stream)).nameToLongName(std.testing.allocator, case[0..]);
defer std.testing.allocator.free(long_name);
const actual = try Fat32FS(@TypeOf(stream)).longNameToShortName(long_name, &[_][11]u8{});
try expectEqualSlices(u8, actual[0..], expected[0..]);
}
}
test "Fat32FS.longNameToShortName - invalid short name characters" {
// Using valid long names
const name_cases = [_][]const u8{
"+file.txt",
",file.txt",
";file.txt",
"=file.txt",
"[file.txt",
"]file.txt",
"€file.txt",
};
var stream = &std.io.fixedBufferStream(&[_]u8{});
const expected = "_FILE~1 TXT";
for (name_cases) |case| {
const long_name = try Fat32FS(@TypeOf(stream)).nameToLongName(std.testing.allocator, case[0..]);
defer std.testing.allocator.free(long_name);
const actual = try Fat32FS(@TypeOf(stream)).longNameToShortName(long_name, &[_][11]u8{});
try expectEqualSlices(u8, actual[0..], expected[0..]);
}
}
test "Fat32FS.longNameToShortName - existing name short" {
const excising_names = &[_][11]u8{
"FILE TXT".*,
"FILE~1 TXT".*,
"FILE~A TXT".*,
"FILE~2 TXT".*,
};
var stream = &std.io.fixedBufferStream(&[_]u8{});
const expected = "FILE~3 TXT";
const long_name = try Fat32FS(@TypeOf(stream)).nameToLongName(std.testing.allocator, "file.txt");
defer std.testing.allocator.free(long_name);
const actual = try Fat32FS(@TypeOf(stream)).longNameToShortName(long_name, excising_names);
try expectEqualSlices(u8, actual[0..], expected[0..]);
}
test "Fat32FS.longNameToShortName - existing name short rev" {
const excising_names = &[_][11]u8{
"FILE~2 TXT".*,
"FILE~A TXT".*,
"FILE~1 TXT".*,
"FILE TXT".*,
};
var stream = &std.io.fixedBufferStream(&[_]u8{});
const expected = "FILE~3 TXT";
const long_name = try Fat32FS(@TypeOf(stream)).nameToLongName(std.testing.allocator, "file.txt");
defer std.testing.allocator.free(long_name);
const actual = try Fat32FS(@TypeOf(stream)).longNameToShortName(long_name, excising_names);
try expectEqualSlices(u8, actual[0..], expected[0..]);
}
test "Fat32FS.longNameToShortName - existing name long" {
const excising_names = &[_][11]u8{
"FILEFILETXT".*,
"FILEFI~1TXT".*,
"FILEFI~ATXT".*,
"FILEFI~2TXT".*,
};
var stream = &std.io.fixedBufferStream(&[_]u8{});
const expected = "FILEFI~3TXT";
const long_name = try Fat32FS(@TypeOf(stream)).nameToLongName(std.testing.allocator, "filefilefile.txt");
defer std.testing.allocator.free(long_name);
const actual = try Fat32FS(@TypeOf(stream)).longNameToShortName(long_name, excising_names);
try expectEqualSlices(u8, actual[0..], expected[0..]);
}
test "Fat32FS.longNameToShortName - existing name long no match" {
const excising_names = &[_][11]u8{
"FILEFI~1TXT".*,
"FILEFI~ATXT".*,
"FILEFI~2TXT".*,
};
var stream = &std.io.fixedBufferStream(&[_]u8{});
const expected = "FILEFILETXT";
const long_name = try Fat32FS(@TypeOf(stream)).nameToLongName(std.testing.allocator, "filefile.txt");
defer std.testing.allocator.free(long_name);
const actual = try Fat32FS(@TypeOf(stream)).longNameToShortName(long_name, excising_names);
try expectEqualSlices(u8, actual[0..], expected[0..]);
}
test "Fat32FS.longNameToShortName - trail number to large" {
const excising_names = &[_][11]u8{
"F~999999TXT".*,
};
var stream = &std.io.fixedBufferStream(&[_]u8{});
const long_name = try Fat32FS(@TypeOf(stream)).nameToLongName(std.testing.allocator, "filefilefile.txt");
defer std.testing.allocator.free(long_name);
try expectError(error.InvalidName, Fat32FS(@TypeOf(stream)).longNameToShortName(long_name, excising_names));
}
test "Fat32FS.longNameToShortName - large trail number" {
const excising_names = &[_][11]u8{
"FILE TXT".*,
"FILE~2 TXT".*,
"FILE~3 TXT".*,
"FILE~4 TXT".*,
"FILE~5 TXT".*,
"FILE~6 TXT".*,
"FILE~7 TXT".*,
"FILE~8 TXT".*,
"FILE~9 TXT".*,
};
var stream = &std.io.fixedBufferStream(&[_]u8{});
const expected = "FILE~10 TXT";
const long_name = try Fat32FS(@TypeOf(stream)).nameToLongName(std.testing.allocator, "file.txt");
defer std.testing.allocator.free(long_name);
const actual = try Fat32FS(@TypeOf(stream)).longNameToShortName(long_name, excising_names);
try expectEqualSlices(u8, actual[0..], expected[0..]);
}
test "Fat32FS.longNameToShortName - CP437" {
var stream = &std.io.fixedBufferStream(&[_]u8{});
const expected = [_]u8{0xE0} ++ "LPHA TXT";
const long_name = try Fat32FS(@TypeOf(stream)).nameToLongName(std.testing.allocator, "αlpha.txt");
defer std.testing.allocator.free(long_name);
const actual = try Fat32FS(@TypeOf(stream)).longNameToShortName(long_name, &[_][11]u8{});
try expectEqualSlices(u8, actual[0..], expected[0..]);
}
test "Fat32FS.createLongNameEntry - less than 13 characters" {
var stream = &std.io.fixedBufferStream(&[_]u8{});
// Pre-calculated check fum for file.txt => FILE TXT
const check_sum: u8 = 25;
// Using valid long name
const long_name = try Fat32FS(@TypeOf(stream)).nameToLongName(std.testing.allocator, "file.txt");
defer std.testing.allocator.free(long_name);
const entries = try Fat32FS(@TypeOf(stream)).createLongNameEntry(std.testing.allocator, long_name, check_sum);
defer std.testing.allocator.free(entries);
try expectEqual(entries.len, 1);
const expected = LongName{
.order = 0x41,
.first = [_]u16{'f'} ++ [_]u16{'i'} ++ [_]u16{'l'} ++ [_]u16{'e'} ++ [_]u16{'.'},
.check_sum = check_sum,
.second = [_]u16{'t'} ++ [_]u16{'x'} ++ [_]u16{'t'} ++ [_]u16{ 0x0000, 0xFFFF, 0xFFFF },
.third = [_]u16{ 0xFFFF, 0xFFFF },
};
try expectEqual(entries[0], expected);
}
test "Fat32FS.createLongNameEntry - greater than 13 characters" {
var stream = &std.io.fixedBufferStream(&[_]u8{});
// Pre-calculated check fum for filefilefilefile.txt => FILEFI~1TXT
const check_sum: u8 = 123;
// Using valid long name
const long_name = try Fat32FS(@TypeOf(stream)).nameToLongName(std.testing.allocator, "filefilefilefile.txt");
defer std.testing.allocator.free(long_name);
const entries = try Fat32FS(@TypeOf(stream)).createLongNameEntry(std.testing.allocator, long_name, check_sum);
defer std.testing.allocator.free(entries);
try expectEqual(entries.len, 2);
var expected = [_]LongName{
LongName{
.order = 0x42,
.first = [_]u16{'i'} ++ [_]u16{'l'} ++ [_]u16{'e'} ++ [_]u16{'.'} ++ [_]u16{'t'},
.check_sum = check_sum,
.second = [_]u16{'x'} ++ [_]u16{'t'} ++ [_]u16{ 0x0000, 0xFFFF, 0xFFFF, 0xFFFF },
.third = [_]u16{ 0xFFFF, 0xFFFF },
},
LongName{
.order = 0x01,
.first = [_]u16{'f'} ++ [_]u16{'i'} ++ [_]u16{'l'} ++ [_]u16{'e'} ++ [_]u16{'f'},
.check_sum = check_sum,
.second = [_]u16{'i'} ++ [_]u16{'l'} ++ [_]u16{'e'} ++ [_]u16{'f'} ++ [_]u16{'i'} ++ [_]u16{'l'},
.third = [_]u16{'e'} ++ [_]u16{'f'},
},
};
try expectEqual(entries[0], expected[0]);
try expectEqual(entries[1], expected[1]);
}
test "Fat32FS.createLongNameEntry - max 255 characters" {
var stream = &std.io.fixedBufferStream(&[_]u8{});
// Pre-calculated check fum for A**255 => AAAAAA~1TXT
const check_sum: u8 = 17;
// Using valid long name
const name = [_]u8{'A'} ** 255;
const long_name = try Fat32FS(@TypeOf(stream)).nameToLongName(std.testing.allocator, name[0..]);
defer std.testing.allocator.free(long_name);
const entries = try Fat32FS(@TypeOf(stream)).createLongNameEntry(std.testing.allocator, long_name, check_sum);
defer std.testing.allocator.free(entries);
try expectEqual(entries.len, 20);
const UA = [_]u16{'A'};
var expected = [_]LongName{LongName{
.order = 0x00,
.first = UA ** 5,
.check_sum = check_sum,
.second = UA ** 6,
.third = UA ** 2,
}} ** 20;
for (expected) |*e, i| {
e.order = 20 - @intCast(u8, i);
}
expected[0] = LongName{
.order = 0x54, // 0x40 | 0x14
.first = UA ** 5,
.check_sum = check_sum,
.second = UA ** 3 ++ [_]u16{ 0x0000, 0xFFFF, 0xFFFF },
.third = [_]u16{ 0xFFFF, 0xFFFF },
};
for (expected) |ex, i| {
try expectEqual(entries[i], ex);
}
}
test "Fat32FS.createShortNameEntry" {
var stream = &std.io.fixedBufferStream(&[_]u8{});
const actual = Fat32FS(@TypeOf(stream)).createShortNameEntry("FILE TXT".*, .None, 0x10);
// Expects 12:12:13 12/12/2012 from mock arch
const expected = ShortName{
.name = "FILE ".*,
.extension = "TXT".*,
.attributes = 0x00,
.time_created_tenth = 0x64, // 100 (1 sec)
.time_created = 0x6186,
.date_created = 0x418C,
.date_last_access = 0x418C,
.cluster_high = 0x00,
.time_last_modification = 0x6186,
.date_last_modification = 0x418C,
.cluster_low = 0x10,
.size = 0x00000000,
};
try expectEqual(actual, expected);
}
test "Fat32FS.writeEntries - all free cluster" {
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 1
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Backup FAT region 1
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Data region
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 test_fs = try testFAT32FS(std.testing.allocator, stream, fat_config);
defer test_fs.destroy() catch unreachable;
const entries = FatDirEntry{
.short_entry = Fat32FS(@TypeOf(stream)).createShortNameEntry("FILE TXT".*, .None, 3),
.long_entry = &[_]LongName{},
};
// Convert to bytes
var expected_bytes: [32]u8 = undefined;
initBytes(ShortName, entries.short_entry, expected_bytes[0..]);
_ = try test_fs.writeEntries(entries, 2, 3, 0);
try expectEqualSlices(u8, expected_bytes[0..], buff_stream[64..]);
}
test "Fat32FS.writeEntries - half free cluster" {
const fat_config = FATConfig{
.bytes_per_sector = 64,
.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 1
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x0F, 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, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Backup FAT region 1
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x0F, 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, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Data region
0x49, 0x4E, 0x53, 0x41, 0x4E, 0x45, 0x7E, 0x31,
0x54, 0x58, 0x54, 0x20, 0x00, 0x00, 0x9B, 0xB9,
0x88, 0x51, 0x88, 0x51, 0x00, 0x00, 0x9B, 0xB9,
0x88, 0x51, 0x0D, 0x00, 0xE3, 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, 0x00, 0x00, 0x00,
};
var stream = &std.io.fixedBufferStream(buff_stream[0..]);
var test_fs = try testFAT32FS(std.testing.allocator, stream, fat_config);
defer test_fs.destroy() catch unreachable;
const entries = FatDirEntry{
.short_entry = Fat32FS(@TypeOf(stream)).createShortNameEntry("FILE TXT".*, .None, 3),
.long_entry = &[_]LongName{},
};
// Convert to bytes
var expected_bytes: [32]u8 = undefined;
initBytes(ShortName, entries.short_entry, expected_bytes[0..]);
_ = try test_fs.writeEntries(entries, 2, 3, 32);
try expectEqualSlices(u8, expected_bytes[0..], buff_stream[160..]);
}
test "Fat32FS.writeEntries - full cluster" {
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 1
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Backup FAT region 1
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Data region 1
0x49, 0x4E, 0x53, 0x41, 0x4E, 0x45, 0x7E, 0x31,
0x54, 0x58, 0x54, 0x20, 0x00, 0x00, 0x9B, 0xB9,
0x88, 0x51, 0x88, 0x51, 0x00, 0x00, 0x9B, 0xB9,
0x88, 0x51, 0x0D, 0x00, 0xE3, 0x00, 0x00, 0x00,
// Data region 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 test_fs = try testFAT32FS(std.testing.allocator, stream, fat_config);
defer test_fs.destroy() catch unreachable;
const entries = FatDirEntry{
.short_entry = Fat32FS(@TypeOf(stream)).createShortNameEntry("FILE TXT".*, .None, 3),
.long_entry = &[_]LongName{},
};
// Convert to bytes
var expected_bytes: [32]u8 = undefined;
initBytes(ShortName, entries.short_entry, expected_bytes[0..]);
_ = try test_fs.writeEntries(entries, 2, 3, 32);
try expectEqualSlices(u8, expected_bytes[0..], buff_stream[96..]);
try expectEqualSlices(u8, buff_stream[8..16], &[_]u8{ 0x03, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x0F });
try expectEqualSlices(u8, buff_stream[40..48], &[_]u8{ 0x03, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x0F });
}
test "Fat32FS.writeEntries - large entry over 3 clusters" {
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 1
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Backup FAT region 1
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Data region 1
0x49, 0x4E, 0x53, 0x41, 0x4E, 0x45, 0x7E, 0x31,
0x54, 0x58, 0x54, 0x20, 0x00, 0x00, 0x9B, 0xB9,
0x88, 0x51, 0x88, 0x51, 0x00, 0x00, 0x9B, 0xB9,
0x88, 0x51, 0x0D, 0x00, 0xE3, 0x00, 0x00, 0x00,
// Data region 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 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,
// Data region 4
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 test_fs = try testFAT32FS(std.testing.allocator, stream, fat_config);
defer test_fs.destroy() catch unreachable;
const short_entry = Fat32FS(@TypeOf(stream)).createShortNameEntry("FILE TXT".*, .None, 3);
const long_name = try Fat32FS(@TypeOf(stream)).nameToLongName(std.testing.allocator, "filefilefile.txt");
defer std.testing.allocator.free(long_name);
const long_entry = try Fat32FS(@TypeOf(stream)).createLongNameEntry(std.testing.allocator, long_name, short_entry.calcCheckSum());
defer std.testing.allocator.free(long_entry);
try expectEqual(long_entry.len, 2);
const entries = FatDirEntry{
.short_entry = short_entry,
.long_entry = long_entry,
};
// Convert to bytes
var expected_bytes: [96]u8 = undefined;
initBytes(LongName, entries.long_entry[0], expected_bytes[0..32]);
initBytes(LongName, entries.long_entry[1], expected_bytes[32..64]);
initBytes(ShortName, entries.short_entry, expected_bytes[64..]);
_ = try test_fs.writeEntries(entries, 2, 3, 32);
try expectEqualSlices(u8, expected_bytes[0..], buff_stream[96..]);
}
test "Fat32FS.createFileOrDir - create file" {
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 = 2,
.fsinfo_sector = undefined,
.backup_boot_sector = undefined,
.has_fs_info = false,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = 0x0FFFFFFF,
};
var buff_stream = [_]u8{
// FAT region 1
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Backup FAT region 1
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Data region 1 (Root dir long name)
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 2 (File)
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 3 (Root dir short name)
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 test_fs = try testFAT32FS(std.testing.allocator, stream, fat_config);
defer test_fs.destroy() catch unreachable;
const file = try Fat32FS(@TypeOf(stream)).createFileOrDir(test_fs.fs, &test_fs.root_node.node.Dir, "file.txt", false);
defer file.File.close();
const expected_short_entry = Fat32FS(@TypeOf(stream)).createShortNameEntry("FILE TXT".*, .None, 3);
const expected_long_name = try Fat32FS(@TypeOf(stream)).nameToLongName(std.testing.allocator, "file.txt");
defer std.testing.allocator.free(expected_long_name);
const expected_long_entry = try Fat32FS(@TypeOf(stream)).createLongNameEntry(std.testing.allocator, expected_long_name, expected_short_entry.calcCheckSum());
defer std.testing.allocator.free(expected_long_entry);
var temp_buf: [32]u8 = undefined;
initBytes(LongName, expected_long_entry[0], temp_buf[0..]);
try expectEqualSlices(u8, buff_stream[64..96], temp_buf[0..]);
initBytes(ShortName, expected_short_entry, temp_buf[0..]);
try expectEqualSlices(u8, buff_stream[128..], temp_buf[0..]);
// FAT
try expectEqualSlices(u8, buff_stream[8..12], &[_]u8{ 0x04, 0x00, 0x00, 0x00 });
try expectEqualSlices(u8, buff_stream[12..16], &[_]u8{ 0xFF, 0xFF, 0xFF, 0x0F });
try expectEqualSlices(u8, buff_stream[16..20], &[_]u8{ 0xFF, 0xFF, 0xFF, 0x0F });
}
test "Fat32FS.createFileOrDir - create directory" {
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 = 2,
.fsinfo_sector = undefined,
.backup_boot_sector = undefined,
.has_fs_info = false,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = 0x0FFFFFFF,
};
var buff_stream = [_]u8{
// FAT region 1
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Backup FAT region 1
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Data region 1 (Root dir long name)
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 2 (Directory)
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 3 (Root dir short name)
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 test_fs = try testFAT32FS(std.testing.allocator, stream, fat_config);
defer test_fs.destroy() catch unreachable;
const file = try Fat32FS(@TypeOf(stream)).createFileOrDir(test_fs.fs, &test_fs.root_node.node.Dir, "folder", true);
defer file.Dir.close();
const expected_short_entry = Fat32FS(@TypeOf(stream)).createShortNameEntry("FOLDER ".*, .Directory, 3);
const expected_long_name = try Fat32FS(@TypeOf(stream)).nameToLongName(std.testing.allocator, "folder");
defer std.testing.allocator.free(expected_long_name);
const expected_long_entry = try Fat32FS(@TypeOf(stream)).createLongNameEntry(std.testing.allocator, expected_long_name, expected_short_entry.calcCheckSum());
defer std.testing.allocator.free(expected_long_entry);
var temp_buf: [32]u8 = undefined;
initBytes(LongName, expected_long_entry[0], temp_buf[0..]);
try expectEqualSlices(u8, buff_stream[64..96], temp_buf[0..]);
initBytes(ShortName, expected_short_entry, temp_buf[0..]);
try expectEqualSlices(u8, buff_stream[128..], temp_buf[0..]);
// FAT
try expectEqualSlices(u8, buff_stream[8..12], &[_]u8{ 0x04, 0x00, 0x00, 0x00 });
try expectEqualSlices(u8, buff_stream[12..16], &[_]u8{ 0xFF, 0xFF, 0xFF, 0x0F });
try expectEqualSlices(u8, buff_stream[16..20], &[_]u8{ 0xFF, 0xFF, 0xFF, 0x0F });
}
test "Fat32FS.createFileOrDir - create file parent cluster full" {
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 = 2,
.fsinfo_sector = undefined,
.backup_boot_sector = undefined,
.has_fs_info = false,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = 0x0FFFFFFF,
};
var buff_stream = [_]u8{
// FAT region 1
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Backup FAT region 1
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Data region 1 (Root dir full)
0x49, 0x4E, 0x53, 0x41, 0x4E, 0x45, 0x7E, 0x31,
0x54, 0x58, 0x54, 0x20, 0x00, 0x00, 0x9B, 0xB9,
0x88, 0x51, 0x88, 0x51, 0x00, 0x00, 0x9B, 0xB9,
0x88, 0x51, 0x0D, 0x00, 0xE3, 0x00, 0x00, 0x00,
// Data region 2 (File)
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 3 (Root dir long name)
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 4 (Root dir short name)
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 test_fs = try testFAT32FS(std.testing.allocator, stream, fat_config);
defer test_fs.destroy() catch unreachable;
const file = try Fat32FS(@TypeOf(stream)).createFileOrDir(test_fs.fs, &test_fs.root_node.node.Dir, "file.txt", false);
defer file.File.close();
const expected_short_entry = Fat32FS(@TypeOf(stream)).createShortNameEntry("FILE TXT".*, .None, 3);
const expected_long_name = try Fat32FS(@TypeOf(stream)).nameToLongName(std.testing.allocator, "file.txt");
defer std.testing.allocator.free(expected_long_name);
const expected_long_entry = try Fat32FS(@TypeOf(stream)).createLongNameEntry(std.testing.allocator, expected_long_name, expected_short_entry.calcCheckSum());
defer std.testing.allocator.free(expected_long_entry);
var temp_buf: [32]u8 = undefined;
initBytes(LongName, expected_long_entry[0], temp_buf[0..]);
try expectEqualSlices(u8, buff_stream[128..160], temp_buf[0..]);
initBytes(ShortName, expected_short_entry, temp_buf[0..]);
try expectEqualSlices(u8, buff_stream[160..], temp_buf[0..]);
// FAT
try expectEqualSlices(u8, buff_stream[8..12], &[_]u8{ 0x04, 0x00, 0x00, 0x00 });
try expectEqualSlices(u8, buff_stream[12..16], &[_]u8{ 0xFF, 0xFF, 0xFF, 0x0F });
try expectEqualSlices(u8, buff_stream[16..20], &[_]u8{ 0x05, 0x00, 0x00, 0x00 });
try expectEqualSlices(u8, buff_stream[20..24], &[_]u8{ 0xFF, 0xFF, 0xFF, 0x0F });
}
test "Fat32FS.createFileOrDir - half root" {
const fat_config = FATConfig{
.bytes_per_sector = 64,
.sectors_per_cluster = 1,
.reserved_sectors = 0,
.hidden_sectors = undefined,
.total_sectors = undefined,
.sectors_per_fat = 1,
.root_directory_cluster = 2,
.fsinfo_sector = undefined,
.backup_boot_sector = undefined,
.has_fs_info = false,
.number_free_clusters = undefined,
.next_free_cluster = undefined,
.cluster_end_marker = 0x0FFFFFFF,
};
var buff_stream = [_]u8{
// FAT region 1
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x0F, 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, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Backup FAT region 1
0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x0F, 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, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Data region 1 (Root long)
0x49, 0x4E, 0x53, 0x41, 0x4E, 0x45, 0x7E, 0x31,
0x54, 0x58, 0x54, 0x20, 0x00, 0x00, 0x9B, 0xB9,
0x88, 0x51, 0x88, 0x51, 0x00, 0x00, 0x9B, 0xB9,
0x88, 0x51, 0x0D, 0x00, 0xE3, 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, 0x00, 0x00, 0x00,
// Data region 2 (File)
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,
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 2 (Root short half)
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,
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 test_fs = try testFAT32FS(std.testing.allocator, stream, fat_config);
defer test_fs.destroy() catch unreachable;
const file = try Fat32FS(@TypeOf(stream)).createFileOrDir(test_fs.fs, &test_fs.root_node.node.Dir, "file.txt", false);
defer file.File.close();
const expected_short_entry = Fat32FS(@TypeOf(stream)).createShortNameEntry("FILE TXT".*, .None, 3);
const expected_long_name = try Fat32FS(@TypeOf(stream)).nameToLongName(std.testing.allocator, "file.txt");
defer std.testing.allocator.free(expected_long_name);
const expected_long_entry = try Fat32FS(@TypeOf(stream)).createLongNameEntry(std.testing.allocator, expected_long_name, expected_short_entry.calcCheckSum());
defer std.testing.allocator.free(expected_long_entry);
var temp_buf: [32]u8 = undefined;
initBytes(LongName, expected_long_entry[0], temp_buf[0..]);
try expectEqualSlices(u8, buff_stream[160..192], temp_buf[0..]);
initBytes(ShortName, expected_short_entry, temp_buf[0..]);
try expectEqualSlices(u8, buff_stream[256..288], temp_buf[0..]);
// FAT
try expectEqualSlices(u8, buff_stream[8..12], &[_]u8{ 0x04, 0x00, 0x00, 0x00 });
try expectEqualSlices(u8, buff_stream[12..16], &[_]u8{ 0xFF, 0xFF, 0xFF, 0x0F });
try expectEqualSlices(u8, buff_stream[16..20], &[_]u8{ 0xFF, 0xFF, 0xFF, 0x0F });
}
test "Fat32FS.write - small file" {
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(.{ .image_size = test_file_buf.len }, stream, false);
var test_fs = try initialiseFAT32(std.testing.allocator, stream);
defer test_fs.destroy() catch unreachable;
try vfs.setRoot(test_fs.root_node.node);
const file = try vfs.openFile("/file.txt", .CREATE_FILE);
const text = "Hello, world!\n";
const written = try file.write(text[0..]);
try expectEqual(written, text.len);
var read_buf1: [text.len * 2]u8 = undefined;
const read1 = try file.read(read_buf1[0..]);
try expectEqual(read1, text.len);
try expectEqualSlices(u8, text[0..], read_buf1[0..read1]);
file.close();
const read_file = try vfs.openFile("/file.txt", .NO_CREATION);
defer read_file.close();
var read_buf2: [text.len * 2]u8 = undefined;
const read2 = try read_file.read(read_buf2[0..]);
try expectEqual(read2, text.len);
try expectEqualSlices(u8, text[0..], read_buf2[0..read2]);
}
test "Fat32FS.write - large file" {
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(.{ .image_size = test_file_buf.len }, stream, false);
var test_fs = try initialiseFAT32(std.testing.allocator, stream);
defer test_fs.destroy() catch unreachable;
try vfs.setRoot(test_fs.root_node.node);
const file = try vfs.openFile("/file.txt", .CREATE_FILE);
// Check the opened file
const open_info1 = test_fs.opened_files.get(@ptrCast(*const vfs.Node, file)).?;
try expectEqual(open_info1.cluster, 3);
try expectEqual(open_info1.size, 0);
try expectEqual(open_info1.entry_cluster, 2);
try expectEqual(open_info1.entry_offset, 60);
const fat_offset = test_fs.fat_config.reserved_sectors * test_fs.fat_config.bytes_per_sector + 12;
try expectEqualSlices(u8, test_file_buf[fat_offset .. fat_offset + 4], &[_]u8{ 0xFF, 0xFF, 0xFF, 0x0F });
const text = [_]u8{'A'} ** (8 * 1024);
const written = try file.write(text[0..]);
try expectEqual(written, text.len);
// Check the FAT
const expected_fat = [_]u32{ 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x0FFFFFFF };
try expectEqualSlices(u8, test_file_buf[fat_offset .. fat_offset + (16 * 4)], std.mem.sliceAsBytes(expected_fat[0..]));
var read_buf1: [text.len * 2]u8 = undefined;
const read1 = try file.read(read_buf1[0..]);
try expectEqual(read1, text.len);
try expectEqualSlices(u8, text[0..], read_buf1[0..read1]);
file.close();
const read_file = try vfs.openFile("/file.txt", .NO_CREATION);
defer read_file.close();
const open_info2 = test_fs.opened_files.get(@ptrCast(*const vfs.Node, read_file)).?;
try expectEqual(open_info2.cluster, 3);
try expectEqual(open_info2.size, text.len);
try expectEqual(open_info2.entry_cluster, 2);
try expectEqual(open_info2.entry_offset, 60);
var read_buf2: [text.len * 2]u8 = undefined;
const read2 = try read_file.read(read_buf2[0..]);
try expectEqual(read2, text.len);
try expectEqualSlices(u8, text[0..], read_buf2[0..read2]);
}
fn testWriteRec(dir_node: *const vfs.DirNode, path: []const u8) anyerror!void {
var test_files = try std.fs.cwd().openDir(path, .{ .iterate = true });
defer test_files.close();
var it = test_files.iterate();
while (try it.next()) |file| {
if (file.kind == .Directory) {
var dir_path = try std.testing.allocator.alloc(u8, path.len + file.name.len + 1);
defer std.testing.allocator.free(dir_path);
std.mem.copy(u8, dir_path[0..], path);
dir_path[path.len] = '/';
std.mem.copy(u8, dir_path[path.len + 1 ..], file.name);
const new_dir = &(try dir_node.open(file.name, .CREATE_DIR, .{})).Dir;
defer new_dir.close();
try testWriteRec(new_dir, dir_path);
} else {
// 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);
const open_file = &(try dir_node.open(file.name, .CREATE_FILE, .{})).File;
defer open_file.close();
// Write the content
const written = try open_file.write(test_file_content);
if (written != test_file_content.len) {
return error.BadWrite;
}
}
}
}
test "Fat32FS.write - 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(.{ .image_size = test_file_buf.len }, stream, false);
var test_fs = try initialiseFAT32(std.testing.allocator, stream);
defer test_fs.destroy() catch unreachable;
try vfs.setRoot(test_fs.root_node.node);
try testWriteRec(&test_fs.root_node.node.Dir, "test/fat32/test_files");
const image = try std.fs.cwd().createFile("ig.img", .{});
defer image.close();
_ = try image.writer().writeAll(test_file_buf);
try testReadRec(&test_fs.root_node.node.Dir, "test/fat32/test_files", true);
}
test "Fat32FS.write - not enough space" {
var test_file_buf = try std.testing.allocator.alloc(u8, 37 * 512);
defer std.testing.allocator.free(test_file_buf);
var stream = &std.io.fixedBufferStream(test_file_buf[0..]);
try mkfat32.Fat32.make(.{ .image_size = test_file_buf.len }, stream, false);
var test_fs = try initialiseFAT32(std.testing.allocator, stream);
defer test_fs.destroy() catch unreachable;
try vfs.setRoot(test_fs.root_node.node);
const text = [_]u8{'A'} ** 1025;
const file = try vfs.openFile("/file.txt", .CREATE_FILE);
defer file.close();
try expectError(error.Unexpected, file.write(text[0..]));
const offset = test_fs.fat_config.clusterToSector(3) * test_fs.fat_config.bytes_per_sector;
try expectEqualSlices(u8, test_file_buf[offset .. offset + 1024], &[_]u8{0x00} ** 1024);
}
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() catch unreachable;
}
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);
_ = 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;
try expectError(error.BadMBRMagic, initialiseFAT32(std.testing.allocator, stream));
test_file_buf[510] = 0x55;
test_file_buf[511] = 0x00;
try 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;
try expectError(error.BadRootCluster, initialiseFAT32(std.testing.allocator, stream));
test_file_buf[44] = 1;
try expectError(error.BadRootCluster, initialiseFAT32(std.testing.allocator, stream));
test_file_buf[44] = 2;
// BadFATCount
test_file_buf[16] = 0;
try expectError(error.BadFATCount, initialiseFAT32(std.testing.allocator, stream));
test_file_buf[16] = 1;
try expectError(error.BadFATCount, initialiseFAT32(std.testing.allocator, stream));
test_file_buf[16] = 10;
try expectError(error.BadFATCount, initialiseFAT32(std.testing.allocator, stream));
test_file_buf[16] = 2;
// NotMirror
test_file_buf[40] = 1;
try expectError(error.NotMirror, initialiseFAT32(std.testing.allocator, stream));
test_file_buf[40] = 10;
try expectError(error.NotMirror, initialiseFAT32(std.testing.allocator, stream));
test_file_buf[40] = 0;
// BadMedia
test_file_buf[21] = 0xF0;
try expectError(error.BadMedia, initialiseFAT32(std.testing.allocator, stream));
test_file_buf[21] = 0xF8;
// BadFat32
test_file_buf[17] = 10;
try expectError(error.BadFat32, initialiseFAT32(std.testing.allocator, stream));
test_file_buf[17] = 0;
test_file_buf[19] = 10;
try expectError(error.BadFat32, initialiseFAT32(std.testing.allocator, stream));
test_file_buf[19] = 0;
test_file_buf[22] = 10;
try expectError(error.BadFat32, initialiseFAT32(std.testing.allocator, stream));
test_file_buf[22] = 0;
// BadSignature
test_file_buf[66] = 0x28;
try 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';
try expectError(error.BadFSType, initialiseFAT32(std.testing.allocator, stream));
test_file_buf[85] = '3';
test_file_buf[86] = '2';
// Test the bad reads
// Boot sector
try expectError(error.BadRead, initialiseFAT32(std.testing.allocator, &std.io.fixedBufferStream(test_file_buf[0..510])));
// FSInfo (we have one)
try expectError(error.BadRead, initialiseFAT32(std.testing.allocator, &std.io.fixedBufferStream(test_file_buf[0 .. 512 + 100])));
// FAT
try 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);
try 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() catch unreachable;
// 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 = 65473,
.next_free_cluster = 53,
.cluster_end_marker = 0x0FFFFFFF,
};
try expectEqual(test_fs.fat_config, expected);
}
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);
_ = 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() catch unreachable;
// 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,
};
try expectEqual(test_fs.fat_config, expected);
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() catch unreachable;
// 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,
};
try expectEqual(test_fs.fat_config, expected);
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() catch unreachable;
// 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 = 53,
.cluster_end_marker = 0x0FFFFFFF,
};
try expectEqual(test_fs.fat_config, expected);
}
}