diff --git a/.gitignore b/.gitignore index e0a6c17..bee6ac7 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ # Custom ignore **/mock_framework.zig **/*.ramdisk +**/*.img diff --git a/mkfat32.zig b/mkfat32.zig index e5c3267..b8ea3a3 100644 --- a/mkfat32.zig +++ b/mkfat32.zig @@ -523,8 +523,8 @@ pub const Fat32 = struct { } // Valid clusters are 1, 2, 4, 8, 16, 32, 64 and 128 - if (options.cluster_size < 0 or options.cluster_size > 128 or !std.math.isPowerOfTwo(options.cluster_size)) { - log.err("Sectors per cluster is invalid. Must be less then 32 and a power of 2. Found: {}", .{options.cluster_size}); + if (options.cluster_size < 1 or options.cluster_size > 128 or !std.math.isPowerOfTwo(options.cluster_size)) { + log.err("Sectors per cluster is invalid. Must be less then or equal to 128 and a power of 2. Found: {}", .{options.cluster_size}); return Error.InvalidOptionValue; } diff --git a/src/kernel/arch/x86/arch.zig b/src/kernel/arch/x86/arch.zig index aad9cbf..6864f5a 100644 --- a/src/kernel/arch/x86/arch.zig +++ b/src/kernel/arch/x86/arch.zig @@ -30,6 +30,9 @@ const MemProfile = mem.MemProfile; /// The type of a device. pub const Device = pci.PciDeviceInfo; +/// The type of the date and time structure. +pub const DateTime = rtc.DateTime; + /// The virtual end of the kernel code. extern var KERNEL_VADDR_END: *u32; @@ -565,10 +568,32 @@ pub fn initTask(task: *Task, entry_point: usize, allocator: *Allocator) Allocato task.stack_pointer = @ptrToInt(&stack.*[kernel_stack_bottom]); } +/// +/// Get a list of hardware devices attached to the system. +/// +/// Arguments: +/// IN allocator: *Allocator - An allocator for getting the devices +/// +/// Return: []Device +/// A list of hardware devices. +/// +/// Error: Allocator.Error +/// OutOfMemory - Unable to allocate space the operation. +/// pub fn getDevices(allocator: *Allocator) Allocator.Error![]Device { return pci.getDevices(allocator); } +/// +/// Get the system date and time. +/// +/// Return: DateTime +/// The current date and time. +/// +pub fn getDateTime() DateTime { + return rtc.getDateTime(); +} + /// /// Initialise the architecture /// diff --git a/src/kernel/arch/x86/rtc.zig b/src/kernel/arch/x86/rtc.zig index 4c6793e..6241cfb 100644 --- a/src/kernel/arch/x86/rtc.zig +++ b/src/kernel/arch/x86/rtc.zig @@ -24,8 +24,14 @@ const CURRENT_CENTURY: u32 = 2000; /// could report that the CMOS chip is faulty or the battery is dyeing. const CENTURY_REGISTER: bool = false; +/// The error set that can be returned from some RTC functions. +const RtcError = error{ + /// If setting the rate for interrupts is less than 3 or greater than 15. + RateError, +}; + /// A structure to hold all the date and time information in the RTC. -const DateTime = struct { +pub const DateTime = struct { second: u32, minute: u32, hour: u32, @@ -36,12 +42,6 @@ const DateTime = struct { day_of_week: u32, }; -/// The error set that can be returned from some RTC functions. -const RtcError = error{ - /// If setting the rate for interrupts is less than 3 or greater than 15. - RateError, -}; - /// The number of ticks that has passed when RTC was initially set up. var ticks: u32 = 0; @@ -145,66 +145,6 @@ fn readRtcRegisters() DateTime { return date_time; } -/// -/// Read a stable time from the real time clock registers on the CMOS chip and return a BCD and -/// 12 hour converted date and time. -/// -/// Return: DateTime -/// The data from the CMOS RTC registers with correct BCD conversions, 12 hour conversions and -/// the century added to the year. -/// -fn readRtc() DateTime { - var date_time1 = readRtcRegisters(); - var date_time2 = readRtcRegisters(); - - // Use the method: Read the registers twice and check if they are the same so to avoid - // inconsistent values due to RTC updates - - var compare = false; - - inline for (@typeInfo(DateTime).Struct.fields) |field| { - compare = compare or @field(date_time1, field.name) != @field(date_time2, field.name); - } - - while (compare) { - date_time1 = readRtcRegisters(); - date_time2 = readRtcRegisters(); - - compare = false; - inline for (@typeInfo(DateTime).Struct.fields) |field| { - compare = compare or @field(date_time1, field.name) != @field(date_time2, field.name); - } - } - - // Convert BCD to binary if necessary - if (isBcd()) { - date_time1.second = bcdToBinary(date_time1.second); - date_time1.minute = bcdToBinary(date_time1.minute); - // Needs a special calculation because the upper bit is set - date_time1.hour = ((date_time1.hour & 0x0F) + (((date_time1.hour & 0x70) / 16) * 10)) | (date_time1.hour & 0x80); - date_time1.day = bcdToBinary(date_time1.day); - date_time1.month = bcdToBinary(date_time1.month); - date_time1.year = bcdToBinary(date_time1.year); - if (CENTURY_REGISTER) { - date_time1.century = bcdToBinary(date_time1.century); - } - } - - // Need to add on the century to the year - if (CENTURY_REGISTER) { - date_time1.year += date_time1.century * 100; - } else { - date_time1.year += CURRENT_CENTURY; - } - - // Convert to 24hr time - if (is12Hr(date_time1)) { - date_time1.hour = ((date_time1.hour & 0x7F) + 12) % 24; - } - - return date_time1; -} - /// /// The interrupt handler for the RTC. /// @@ -262,6 +202,66 @@ fn enableInterrupts() void { cmos.writeStatusRegister(cmos.StatusRegister.B, status_b | 0x40, true); } +/// +/// Read a stable time from the real time clock registers on the CMOS chip and return a BCD and +/// 12 hour converted date and time. +/// +/// Return: DateTime +/// The data from the CMOS RTC registers with correct BCD conversions, 12 hour conversions and +/// the century added to the year. +/// +pub fn getDateTime() DateTime { + var date_time1 = readRtcRegisters(); + var date_time2 = readRtcRegisters(); + + // Use the method: Read the registers twice and check if they are the same so to avoid + // inconsistent values due to RTC updates + + var compare = false; + + inline for (@typeInfo(DateTime).Struct.fields) |field| { + compare = compare or @field(date_time1, field.name) != @field(date_time2, field.name); + } + + while (compare) { + date_time1 = readRtcRegisters(); + date_time2 = readRtcRegisters(); + + compare = false; + inline for (@typeInfo(DateTime).Struct.fields) |field| { + compare = compare or @field(date_time1, field.name) != @field(date_time2, field.name); + } + } + + // Convert BCD to binary if necessary + if (isBcd()) { + date_time1.second = bcdToBinary(date_time1.second); + date_time1.minute = bcdToBinary(date_time1.minute); + // Needs a special calculation because the upper bit is set + date_time1.hour = ((date_time1.hour & 0x0F) + (((date_time1.hour & 0x70) / 16) * 10)) | (date_time1.hour & 0x80); + date_time1.day = bcdToBinary(date_time1.day); + date_time1.month = bcdToBinary(date_time1.month); + date_time1.year = bcdToBinary(date_time1.year); + if (CENTURY_REGISTER) { + date_time1.century = bcdToBinary(date_time1.century); + } + } + + // Need to add on the century to the year + if (CENTURY_REGISTER) { + date_time1.year += date_time1.century * 100; + } else { + date_time1.year += CURRENT_CENTURY; + } + + // Convert to 24hr time + if (is12Hr(date_time1)) { + date_time1.hour = ((date_time1.hour & 0x7F) + 12) % 24; + } + + return date_time1; +} + /// /// Initialise the RTC. /// @@ -558,7 +558,7 @@ test "readRtc unstable read" { .century = 2000, .day_of_week = 5, }; - const actual = readRtc(); + const actual = getDateTime(); expectEqual(expected, actual); } @@ -611,7 +611,7 @@ test "readRtc is BCD" { .century = 2000, .day_of_week = 5, }; - const actual = readRtc(); + const actual = getDateTime(); expectEqual(expected, actual); } @@ -664,7 +664,7 @@ test "readRtc is 12 hours" { .century = 2000, .day_of_week = 5, }; - const actual = readRtc(); + const actual = getDateTime(); expectEqual(expected, actual); } diff --git a/src/kernel/filesystem/fat32.zig b/src/kernel/filesystem/fat32.zig index 623db3e..b28a0d5 100644 --- a/src/kernel/filesystem/fat32.zig +++ b/src/kernel/filesystem/fat32.zig @@ -8,6 +8,7 @@ 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; @@ -376,7 +377,7 @@ const ShortName = struct { } /// - /// Calculate the check sum for the long name entry. + /// Calculate the check sum for the short name entry. /// /// Arguments: /// IN self: *const ShortName - The short entry to calculate the check sum for. @@ -514,6 +515,48 @@ fn initStruct(comptime Type: type, bytes: []const u8) 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. /// @@ -576,6 +619,10 @@ pub fn Fat32FS(comptime StreamType: type) type { /// 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. @@ -592,6 +639,12 @@ pub fn Fat32FS(comptime StreamType: type) type { /// 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. @@ -628,6 +681,12 @@ pub fn Fat32FS(comptime StreamType: type) type { /// 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 @@ -659,7 +718,7 @@ pub fn Fat32FS(comptime StreamType: type) type { else => StreamType.GetPosError, }; - /// An iterator for looping over the cluster chain in the FAT. + /// 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, @@ -744,7 +803,7 @@ pub fn Fat32FS(comptime StreamType: type) type { /// pub fn read(self: *ClusterChainIteratorSelf, buff: []u8) (Fat32Self.Error || ReadError || SeekError)!?usize { // FAT32 is really FAT28, so the top 4 bits are not used, so mask them out - if (buff.len != 0 and self.cluster != 0 and (self.cluster & 0x0FFFFFFF) < self.fat_config.cluster_end_marker and buff.len > 0) { + 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); @@ -773,8 +832,7 @@ pub fn Fat32FS(comptime StreamType: type) type { /// Deinitialise the cluster chain iterator. /// /// Arguments: - /// IN self: *ClusterChainIteratorSelf - Iterator self. - /// + /// IN self: *const ClusterChainIteratorSelf - Iterator self. /// pub fn deinit(self: *const ClusterChainIteratorSelf) void { self.allocator.free(self.fat); @@ -803,8 +861,7 @@ pub fn Fat32FS(comptime StreamType: type) type { var fat = try allocator.alloc(u32, fat_config.bytes_per_sector / @sizeOf(u32)); errdefer allocator.free(fat); - // FAT32 has u32 entries so need to divide - const table_offset = cluster / (fat_config.bytes_per_sector / @sizeOf(u32)); + const table_offset = cluster / fat.len; // Seek to the FAT // The FAT is just after the reserved sectors + the index @@ -949,8 +1006,8 @@ pub fn Fat32FS(comptime StreamType: type) type { var long_entries: ?[]LongName = null; defer if (long_entries) |entries| self.allocator.free(entries); - // If attribute is 0x0F, then is a long file name and - if (self.cluster_block[self.index] & 0x40 == 0x40 and self.cluster_block[self.index + 11] == 0x0F) { + // 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 @@ -989,7 +1046,7 @@ pub fn Fat32FS(comptime StreamType: type) type { // Easy check for the attributes as 0x0F is invalid and a long name part // So if we have one, then this is a long entry not short entry as expected // Also make are we are not at the end - if (self.cluster_block[self.index] != 0x00 and self.cluster_block[self.index + 11] == 0x0F) { + 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; @@ -1147,43 +1204,79 @@ pub fn Fat32FS(comptime StreamType: type) type { const self = @fieldParentPtr(Fat32Self, "instance", fs.instance); const cast_node = @ptrCast(*const vfs.Node, node); const opened_node = self.opened_files.get(cast_node) orelse return vfs.Error.NotOpened; - // TODO: Future PR - return 0; + // 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 { return switch (flags) { .NO_CREATION => openImpl(fs, dir, name), - .CREATE_DIR, .CREATE_FILE, .CREATE_SYMLINK => createFileOrDirOrSymlink(fs, dir, name, flags, open_args), + .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 + /// 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 - When creating a file, the cluster the file is at. - /// IN size: u32 - When creating a file, the size of the file is at. - /// IN flags: vfs.OpenFlags - The open flags for deciding on the Node type. - /// IN open_args: vfs.OpenArgs - The open arguments when creating a symlink. + /// 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.NoSymlinkTarget - See vfs.Error.NoSymlinkTarget. + /// 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, flags: vfs.OpenFlags, open_args: vfs.OpenArgs) (Allocator.Error || vfs.Error)!*vfs.Node { + 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 => return vfs.Error.InvalidFlags, - .NO_CREATION => unreachable, + .CREATE_SYMLINK, .NO_CREATION => return vfs.Error.InvalidFlags, }; // Create the opened info struct @@ -1192,6 +1285,8 @@ pub fn Fat32FS(comptime StreamType: type) type { opened_info.* = .{ .cluster = cluster, .size = size, + .entry_cluster = entry_cluster, + .entry_offset = entry_offset, }; try self.opened_files.put(node, opened_info); @@ -1211,7 +1306,7 @@ pub fn Fat32FS(comptime StreamType: type) type { /// Error: vfs.Error /// error.NotOpened - If directory node isn't opened. /// - fn getDirCluster(self: *Fat32Self, dir: *const vfs.DirNode) vfs.Error!u32 { + 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 @@ -1244,6 +1339,7 @@ pub fn Fat32FS(comptime StreamType: type) type { // 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 => { @@ -1255,11 +1351,15 @@ pub fn Fat32FS(comptime StreamType: type) type { while (it.next() catch |e| switch (e) { error.OutOfMemory => return error.OutOfMemory, else => { - log.err("Error initialising the entry iterator. Error: {}\n", .{e}); + 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| { @@ -1277,12 +1377,110 @@ pub fn Fat32FS(comptime StreamType: type) type { if (match) { const open_type = if (entry.short_name.isDir()) vfs.OpenFlags.CREATE_DIR else vfs.OpenFlags.CREATE_FILE; - return self.createNode(entry.short_name.getCluster(), entry.short_name.size, open_type, .{}); + return 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. /// @@ -1290,21 +1488,561 @@ pub fn Fat32FS(comptime StreamType: type) type { /// IN fs: *const vfs.FileSystem - The underlying filesystem. /// IN dir: *const vfs.DirNode - The parent directory. /// IN name: []const u8 - The name of the file/folder to open. - /// IN flags: vfs.OpenFlags - The open flags for if creating a file/folder/symlink. - /// IN open_args: vfs.OpenArgs - The open arguments if creating a symlink. + /// IN is_dir: bool - If creating a file/folder. /// /// Return: *vfs.Node /// The VFS Node for the opened/created file/folder. /// - /// Error: Allocator.Error || ReadError || SeekError || vfs.Error - /// Allocator.Error - Not enough memory for allocating memory + /// 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 createFileOrDirOrSymlink(fs: *const vfs.FileSystem, dir: *const vfs.DirNode, name: []const u8, flags: vfs.OpenFlags, open_args: vfs.OpenArgs) (Allocator.Error || vfs.Error)!*vfs.Node { + 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); - // TODO: Future PR - return vfs.Error.NoSuchFileOrDir; + 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; + }, + }; + + // Calculate the offset where the file size is stored on disk + const file_size_offset = previous_index + (entries.long_entry.len * 32) + 28; + + 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, false, .{}); + + // 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; } /// @@ -1314,10 +2052,11 @@ pub fn Fat32FS(comptime StreamType: type) type { /// Arguments: /// IN self: *Fat32Self - Self to free. /// - pub fn destroy(self: *Fat32Self) void { + pub fn destroy(self: *Fat32Self) Fat32Self.Error!void { // Make sure we have closed all files - // TODO: Should this deinit close any open files instead? - std.debug.assert(self.opened_files.count() == 0); + 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); @@ -1521,46 +2260,38 @@ pub fn initialiseFAT32(allocator: *Allocator, stream: anytype) (Allocator.Error } /// -/// Read the test files and write them to the test FAT32 filesystem. +/// 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 comptime StreamType: type - The stream type. -/// IN fat32fs: *Fat32FS(StreamType) - The test filesystem. +/// 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. /// -/// Error: Allocator.Error || ErrorSet(StreamType) || vfs.Error || std.fs.File.OpenError || std.fs.File.ReadError -/// Allocator.Error - Error when allocating memory for reading the file content. -/// ErrorSet(StreamType) - Error when writing to the test filesystem. -/// vfs.Error - Error with VFS operations. -/// std.fs.File.OpenError - Error when opening the test files. -/// std.fs.File.ReadError - Error when reading the test files. +/// Return: *Fat32FS(@TypeOf(stream)) +/// Teh test FAT32 filesystem /// -fn testWriteTestFiles(comptime StreamType: type, fat32fs: *Fat32FS(StreamType)) (Allocator.Error || ErrorSet(StreamType) || vfs.Error || std.fs.File.OpenError || std.fs.File.ReadError)!void { - vfs.setRoot(fat32fs.root_node.node); +/// 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 std.testing.allocator.free(test_file_buf); - var test_files = try std.fs.cwd().openDir("test/fat32/test_files", .{ .iterate = true }); - defer test_files.close(); + var temp_stream = &std.io.fixedBufferStream(test_file_buf[0..]); - var it = test_files.iterate(); - while (try it.next()) |file| { - // Open the test file - const test_file = try test_files.openFile(file.name, .{}); - defer test_file.close(); + try mkfat32.Fat32.make(.{ .image_size = test_file_buf.len }, temp_stream, true); - // Read the content - const test_file_content = try test_file.readToEndAlloc(std.testing.allocator, 0xFFFF); - defer std.testing.allocator.free(test_file_content); + var test_fs = try initialiseFAT32(std.testing.allocator, temp_stream); + test_fs.stream = stream; + test_fs.fat_config = fat_config; - // TODO: Once the write PR is complete, then write the files - // // Open the test file to the FAT32 filesystem - // const f1 = try vfs.openFile(file, .CREATE_FILE); - // defer f1.close(); + try vfs.setRoot(test_fs.root_node.node); - // // Write the content - // const written = try f1.write(test_file_content); - // if (written != test_file_content.len) { - // @panic("Written not the same for content length"); - // } - } + return test_fs; } test "LongName.getName" { @@ -1926,275 +2657,6 @@ test "ShortName.calcCheckSum" { expectEqual(sfn.calcCheckSum(), 0x7A); } -test "Fat32FS initialise test files" { - var test_file_buf = try std.testing.allocator.alloc(u8, 1024 * 1024); - defer std.testing.allocator.free(test_file_buf); - - var stream = &std.io.fixedBufferStream(test_file_buf[0..]); - - try mkfat32.Fat32.make(.{}, stream, true); - - var test_fs = try initialiseFAT32(std.testing.allocator, stream); - defer test_fs.destroy(); - - // Write the test files - try testWriteTestFiles(@TypeOf(stream), test_fs); - - // Open the known good image to compare to - const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{}); - defer test_fat32_image.close(); - - // TODO: Loop over the test files and open then - // Compare the long and short entries - // The time stamps will be different so can't to complete byte compare -} - -test "Fat32FS.getRootNode" { - const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{}); - defer test_fat32_image.close(); - - var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image); - defer test_fs.destroy(); - - expectEqual(test_fs.fs.getRootNode(test_fs.fs), &test_fs.root_node.node.Dir); - expectEqual(test_fs.root_node.cluster, 2); - expectEqual(test_fs.fat_config.root_directory_cluster, 2); -} - -test "Fat32FS.init no error" { - const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{}); - defer test_fat32_image.close(); - - var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image); - defer test_fs.destroy(); -} - -test "Fat32FS.init errors" { - const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{}); - defer test_fat32_image.close(); - - var test_file_buf = try std.testing.allocator.alloc(u8, (32 * 512 + 4) + 1); - defer std.testing.allocator.free(test_file_buf); - - const read = try test_fat32_image.reader().readAll(test_file_buf[0..]); - const stream = &std.io.fixedBufferStream(test_file_buf[0..]); - - // BadMBRMagic - test_file_buf[510] = 0x00; - expectError(error.BadMBRMagic, initialiseFAT32(std.testing.allocator, stream)); - test_file_buf[510] = 0x55; - - test_file_buf[511] = 0x00; - expectError(error.BadMBRMagic, initialiseFAT32(std.testing.allocator, stream)); - test_file_buf[511] = 0xAA; - - // BadRootCluster - // Little endian, so just eed to set the upper bytes - test_file_buf[44] = 0; - expectError(error.BadRootCluster, initialiseFAT32(std.testing.allocator, stream)); - - test_file_buf[44] = 1; - expectError(error.BadRootCluster, initialiseFAT32(std.testing.allocator, stream)); - test_file_buf[44] = 2; - - // BadFATCount - test_file_buf[16] = 0; - expectError(error.BadFATCount, initialiseFAT32(std.testing.allocator, stream)); - - test_file_buf[16] = 1; - expectError(error.BadFATCount, initialiseFAT32(std.testing.allocator, stream)); - - test_file_buf[16] = 10; - expectError(error.BadFATCount, initialiseFAT32(std.testing.allocator, stream)); - test_file_buf[16] = 2; - - // NotMirror - test_file_buf[40] = 1; - expectError(error.NotMirror, initialiseFAT32(std.testing.allocator, stream)); - - test_file_buf[40] = 10; - expectError(error.NotMirror, initialiseFAT32(std.testing.allocator, stream)); - test_file_buf[40] = 0; - - // BadMedia - test_file_buf[21] = 0xF0; - expectError(error.BadMedia, initialiseFAT32(std.testing.allocator, stream)); - test_file_buf[21] = 0xF8; - - // BadFat32 - test_file_buf[17] = 10; - expectError(error.BadFat32, initialiseFAT32(std.testing.allocator, stream)); - test_file_buf[17] = 0; - - test_file_buf[19] = 10; - expectError(error.BadFat32, initialiseFAT32(std.testing.allocator, stream)); - test_file_buf[19] = 0; - - test_file_buf[22] = 10; - expectError(error.BadFat32, initialiseFAT32(std.testing.allocator, stream)); - test_file_buf[22] = 0; - - // BadSignature - test_file_buf[66] = 0x28; - expectError(error.BadSignature, initialiseFAT32(std.testing.allocator, stream)); - test_file_buf[66] = 0x29; - - // BadFSType - // Change from FAT32 to FAT16 - test_file_buf[85] = '1'; - test_file_buf[86] = '6'; - expectError(error.BadFSType, initialiseFAT32(std.testing.allocator, stream)); - test_file_buf[85] = '3'; - test_file_buf[86] = '2'; - - // Test the bad reads - // Boot sector - expectError(error.BadRead, initialiseFAT32(std.testing.allocator, &std.io.fixedBufferStream(test_file_buf[0..510]))); - // FSInfo (we have one) - expectError(error.BadRead, initialiseFAT32(std.testing.allocator, &std.io.fixedBufferStream(test_file_buf[0 .. 512 + 100]))); - // FAT - expectError(error.BadRead, initialiseFAT32(std.testing.allocator, &std.io.fixedBufferStream(test_file_buf[0 .. (32 * 512 + 4) + 1]))); -} - -test "Fat32FS.init free memory" { - const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{}); - defer test_fat32_image.close(); - - const allocations: usize = 5; - var i: usize = 0; - while (i < allocations) : (i += 1) { - var fa = std.testing.FailingAllocator.init(std.testing.allocator, i); - expectError(error.OutOfMemory, initialiseFAT32(&fa.allocator, test_fat32_image)); - } -} - -test "Fat32FS.init FATConfig expected" { - const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{}); - defer test_fat32_image.close(); - - var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image); - defer test_fs.destroy(); - - // This is the expected FAT config from the initialised FAT device - const expected = FATConfig{ - .bytes_per_sector = 512, - .sectors_per_cluster = 1, - .reserved_sectors = 32, - .hidden_sectors = 0, - .total_sectors = 66583, - .sectors_per_fat = 513, - .root_directory_cluster = 2, - .fsinfo_sector = 1, - .backup_boot_sector = 6, - .has_fs_info = true, - .number_free_clusters = 65490, - .next_free_cluster = 36, - .cluster_end_marker = 0x0FFFFFFF, - }; - - 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); - - const read = try test_fat32_image.reader().readAll(test_file_buf[0..]); - const stream = &std.io.fixedBufferStream(test_file_buf[0..]); - - // No FSInfo - { - // Force no FSInfo - test_file_buf[48] = 0x00; - - var test_fs = try initialiseFAT32(std.testing.allocator, stream); - defer test_fs.destroy(); - - // This is the default config that should be produced from mkfat32.Fat32 - const expected = FATConfig{ - .bytes_per_sector = 512, - .sectors_per_cluster = 1, - .reserved_sectors = 32, - .hidden_sectors = 0, - .total_sectors = 66583, - .sectors_per_fat = 513, - .root_directory_cluster = 2, - .fsinfo_sector = 0, - .backup_boot_sector = 6, - .has_fs_info = false, - .number_free_clusters = 0xFFFFFFFF, - .next_free_cluster = 0xFFFFFFFF, - .cluster_end_marker = 0x0FFFFFFF, - }; - - expectEqual(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(); - - // This is the default config that should be produced from mkfat32.Fat32 - const expected = FATConfig{ - .bytes_per_sector = 512, - .sectors_per_cluster = 1, - .reserved_sectors = 32, - .hidden_sectors = 0, - .total_sectors = 66583, - .sectors_per_fat = 513, - .root_directory_cluster = 2, - .fsinfo_sector = 1, - .backup_boot_sector = 6, - .has_fs_info = false, - .number_free_clusters = 0xFFFFFFFF, - .next_free_cluster = 0xFFFFFFFF, - .cluster_end_marker = 0x0FFFFFFF, - }; - - expectEqual(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(); - - // 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 = 36, - .cluster_end_marker = 0x0FFFFFFF, - }; - - expectEqual(test_fs.fat_config, expected); - } -} - test "ClusterChainIterator.checkRead - Within cluster, within FAT" { // The undefined values are not used in checkRead const fat_config = FATConfig{ @@ -2215,7 +2677,7 @@ test "ClusterChainIterator.checkRead - Within cluster, within FAT" { var buff_stream = [_]u8{}; var stream = &std.io.fixedBufferStream(buff_stream[0..]); // First 2 are for other purposed and not needed, the third is the first real FAT entry - var fat = [_]u32{ 0x0FFFFFFF, 0xFFFFFFF8, 0xFFFFFFFF, 0x00000000 }; + var fat = [_]u32{ 0x0FFFFFFF, 0xFFFFFFF8, 0x0FFFFFFF, 0x00000000 }; var it = Fat32FS(@TypeOf(stream)).ClusterChainIterator{ .allocator = undefined, .cluster = 2, @@ -2256,7 +2718,7 @@ test "ClusterChainIterator.checkRead - Multiple clusters, within FAT" { var buff_stream = [_]u8{}; var stream = &std.io.fixedBufferStream(buff_stream[0..]); // First 2 are for other purposed and not needed, the third is the first real FAT entry - var fat = [_]u32{ 0x0FFFFFFF, 0xFFFFFFF8, 0x00000003, 0xFFFFFFFF }; + var fat = [_]u32{ 0x0FFFFFFF, 0xFFFFFFF8, 0x00000003, 0x0FFFFFFF }; var it = Fat32FS(@TypeOf(stream)).ClusterChainIterator{ .allocator = undefined, .cluster = 2, @@ -2298,20 +2760,16 @@ test "ClusterChainIterator.checkRead - Multiple clusters, outside FAT" { // Set the stream to all FF which represents the end of a FAT chain var buff_stream = [_]u8{ // First 4 FAT (little endian) - 0xFF, 0xFF, 0xFF, 0x0F, - 0xF8, 0xFF, 0xFF, 0xFF, - 0x04, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, + 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, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, + 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, 0xFFFFFFFF }; - var expected_fat = [_]u32{ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; + 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, @@ -2450,25 +2908,19 @@ test "ClusterChainIterator.read - success" { var buff_stream = [_]u8{ // FAT region - 0xFF, 0xFF, 0xFF, 0x0F, - 0xF8, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 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, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, + 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', - '!', '"', '$', '%', + 'a', 'b', 'c', 'd', '1', '2', '3', '4', + 'A', 'B', 'C', 'D', '!', '"', '$', '%', }; var stream = &std.io.fixedBufferStream(buff_stream[0..]); // First 2 are for other purposed and not needed, the third is the first real FAT entry - var fat = [_]u32{ 0x0FFFFFFF, 0xFFFFFFF8, 0xFFFFFFFF, 0xFFFFFFFF }; - var expected_fat = [_]u32{ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; + var fat = [_]u32{ 0x0FFFFFFF, 0xFFFFFFF8, 0x0FFFFFFF, 0x0FFFFFFF }; + var expected_fat = [_]u32{ 0x0FFFFFFF, 0x0FFFFFFF, 0x0FFFFFFF, 0x0FFFFFFF }; var it = Fat32FS(@TypeOf(stream)).ClusterChainIterator{ .allocator = undefined, .cluster = 2, @@ -2486,7 +2938,7 @@ test "ClusterChainIterator.read - success" { expectEqualSlices(u8, buff[0..], "abcd1234ABCD!\"$%"); expectEqual(it.table_offset, 0); expectEqual(it.cluster_offset, 0); - expectEqual(it.cluster, 0xFFFFFFFF); + expectEqual(it.cluster, 0x0FFFFFFF); } test "ClusterChainIterator.init - free on BadRead" { @@ -2527,20 +2979,14 @@ test "ClusterChainIterator.init - free on OutOfMemory" { }; var buff_stream = [_]u8{ // FAT region - 0xFF, 0xFF, 0xFF, 0x0F, - 0xF8, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 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, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, + 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', - '!', '"', '$', '%', + 'a', 'b', 'c', 'd', '1', '2', '3', '4', + 'A', 'B', 'C', 'D', '!', '"', '$', '%', }; var stream = &std.io.fixedBufferStream(buff_stream[0..]); const allocations: usize = 1; @@ -2570,20 +3016,14 @@ test "ClusterChainIterator.init - success and good read" { }; var buff_stream = [_]u8{ // FAT region - 0xFF, 0xFF, 0xFF, 0x0F, - 0xF8, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 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, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, + 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', - '!', '"', '$', '%', + '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); @@ -2597,7 +3037,7 @@ test "ClusterChainIterator.init - success and good read" { expectEqualSlices(u8, buff[0..], "abcd1234ABCD!\"$%"); expectEqual(it.table_offset, 0); expectEqual(it.cluster_offset, 0); - expectEqual(it.cluster, 0xFFFFFFFF); + expectEqual(it.cluster, 0x0FFFFFFF); const expect_null = try it.read(buff[read..]); expectEqual(expect_null, null); @@ -2621,25 +3061,17 @@ test "EntryIterator.checkRead - inside cluster block" { }; var buff_stream = [_]u8{ // FAT region - 0xFF, 0xFF, 0xFF, 0x0F, - 0xF8, 0xFF, 0xFF, 0xFF, - 0x04, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, + 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, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, + 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', - '!', '"', '$', '%', + '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', - '^', '&', '*', '(', + '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); @@ -2678,25 +3110,17 @@ test "EntryIterator.checkRead - read new cluster" { }; var buff_stream = [_]u8{ // FAT region - 0xFF, 0xFF, 0xFF, 0x0F, - 0xF8, 0xFF, 0xFF, 0xFF, - 0x03, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 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, 0xFF, + 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', - '!', '"', '$', '%', + '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', - '^', '&', '*', '(', + '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); @@ -2736,25 +3160,17 @@ test "EntryIterator.checkRead - end of cluster chain" { }; var buff_stream = [_]u8{ // FAT region - 0xFF, 0xFF, 0xFF, 0x0F, - 0xF8, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 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, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, + 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', - '!', '"', '$', '%', + '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', - '^', '&', '*', '(', + '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); @@ -2792,41 +3208,25 @@ test "EntryIterator.nextImp - end of entries" { }; var buff_stream = [_]u8{ // FAT region - 0xFF, 0xFF, 0xFF, 0x0F, - 0xF8, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, + 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, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, + 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 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); @@ -2866,41 +3266,25 @@ test "EntryIterator.nextImp - just deleted files" { // This will also read the next cluster chain. var buff_stream = [_]u8{ // FAT region - 0xFF, 0xFF, 0xFF, 0x0F, - 0xF8, 0xFF, 0xFF, 0xFF, - 0x03, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, + 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, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, + 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, + 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 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); @@ -2940,41 +3324,25 @@ test "EntryIterator.nextImp - short name only" { // This will also read the next cluster chain. var buff_stream = [_]u8{ // FAT region - 0xFF, 0xFF, 0xFF, 0x0F, - 0xF8, 0xFF, 0xFF, 0xFF, - 0x03, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, + 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, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, + 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, + 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 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); @@ -3018,59 +3386,35 @@ test "EntryIterator.nextImp - long name only" { // FAT 4 2 long entries, no associated short var buff_stream = [_]u8{ // FAT region - 0xFF, 0xFF, 0xFF, 0x0F, - 0xF8, 0xFF, 0xFF, 0xFF, - 0x03, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, - 0x05, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, + 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, 0xFF, - 0x05, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, + 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, 0xFF, + 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Data region cluster 3 - 0x41, 0x42, 0x00, 0x73, - 0x00, 0x68, 0x00, 0x6F, - 0x00, 0x72, 0x00, 0x0F, - 0x00, 0xA8, 0x74, 0x00, - 0x2E, 0x00, 0x74, 0x00, - 0x78, 0x00, 0x74, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, + 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, 0xFF, + 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 { @@ -3132,50 +3476,30 @@ test "EntryIterator.nextImp - long name, incorrect check sum" { // this has changed from the valid 0xA8 to a invalid 0x55 check sum var buff_stream = [_]u8{ // FAT region - 0xFF, 0xFF, 0xFF, 0x0F, - 0xF8, 0xFF, 0xFF, 0xFF, - 0x03, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, + 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, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, + 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, 0xFF, + 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, + 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 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); @@ -3218,50 +3542,30 @@ test "EntryIterator.nextImp - long name missing entry" { // this has changed from the valid 0xA8 to a invalid 0x55 check sum var buff_stream = [_]u8{ // FAT region - 0xFF, 0xFF, 0xFF, 0x0F, - 0xF8, 0xFF, 0xFF, 0xFF, - 0x03, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, + 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, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, + 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, 0xFF, + 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, + 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 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); @@ -3299,59 +3603,35 @@ test "EntryIterator.nextImp - valid short and long entry" { // Values taken from a real FAT32 implementation var buff_stream = [_]u8{ // FAT region - 0xFF, 0xFF, 0xFF, 0x0F, - 0xF8, 0xFF, 0xFF, 0xFF, - 0x03, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, + 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, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, + 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, 0xFF, + 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, + 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, + 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, + 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); @@ -3392,78 +3672,46 @@ test "EntryIterator.next - skips orphan long entry" { // Values taken from a real FAT32 implementation var buff_stream = [_]u8{ // FAT region - 0xFF, 0xFF, 0xFF, 0x0F, - 0xF8, 0xFF, 0xFF, 0xFF, - 0x03, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, + 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, 0xFF, + 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, 0xFF, + 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, + 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, + 0x4C, 0x4F, 0x4F, 0x4F, 0x4F, 0x4F, 0x7E, 0x31, + 0x54, 0x58, 0x54, 0x00, 0x18, 0xA0, 0x68, 0xA9, + 0xFE, 0x50, 0x00, 0x00, 0x00, 0x00, 0x6E, 0xA9, + 0xFE, 0x50, 0x08, 0x00, 0x13, 0x00, 0x00, 0x00, // Data region cluster 4 - 0x42, 0x2E, 0x00, 0x74, - 0x00, 0x78, 0x00, 0x74, - 0x00, 0x00, 0x00, 0x0F, - 0x00, 0xE9, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, + 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, + 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, + 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); @@ -3510,20 +3758,14 @@ test "EntryIterator.init - free on OutOfMemory" { }; var buff_stream = [_]u8{ // FAT region - 0xFF, 0xFF, 0xFF, 0x0F, - 0xF8, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 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, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, + 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', - '!', '"', '$', '%', + 'a', 'b', 'c', 'd', '1', '2', '3', '4', + 'A', 'B', 'C', 'D', '!', '"', '$', '%', }; var stream = &std.io.fixedBufferStream(buff_stream[0..]); const allocations: usize = 2; @@ -3553,29 +3795,90 @@ test "EntryIterator.init - free on BadRead" { }; var buff_stream = [_]u8{ // FAT region - 0xFF, 0xFF, 0xFF, 0x0F, - 0xF8, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 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, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, + 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', + 'a', 'b', 'c', 'd', '1', '2', '3', '4', }; var stream = &std.io.fixedBufferStream(buff_stream[0..]); expectError(error.BadRead, Fat32FS(@TypeOf(stream)).EntryIterator.init(std.testing.allocator, fat_config, 2, stream)); } +test "Fat32FS.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; + + expectEqual(test_fs.fs.getRootNode(test_fs.fs), &test_fs.root_node.node.Dir); + expectEqual(test_fs.root_node.cluster, 2); + expectEqual(test_fs.fat_config.root_directory_cluster, 2); +} + +test "Fat32FS.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); + expect(dir_node.isDir()); + expect(test_fs.opened_files.contains(dir_node)); + const opened_info = test_fs.opened_files.remove(dir_node).?.value; + defer std.testing.allocator.destroy(opened_info); + expectEqual(opened_info.cluster, 3); + 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); + expect(file_node.isFile()); + expect(test_fs.opened_files.contains(file_node)); + const opened_info = test_fs.opened_files.remove(file_node).?.value; + defer std.testing.allocator.destroy(opened_info); + expectEqual(opened_info.cluster, 4); + 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; + + 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; + + 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(); + defer test_fs.destroy() catch unreachable; // There 2 allocations var allocations: usize = 0; @@ -3583,7 +3886,7 @@ test "Fat32FS.createNode - free memory" { var fa = std.testing.FailingAllocator.init(std.testing.allocator, allocations); const allocator = &fa.allocator; test_fs.allocator = allocator; - expectError(error.OutOfMemory, test_fs.createNode(3, 16, .CREATE_FILE, .{})); + expectError(error.OutOfMemory, test_fs.createNode(3, 16, 0, 0, .CREATE_FILE)); } } @@ -3592,9 +3895,9 @@ test "Fat32FS.getDirCluster - root dir" { defer test_fat32_image.close(); var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image); - defer test_fs.destroy(); + defer test_fs.destroy() catch unreachable; - var test_node_1 = try test_fs.createNode(3, 16, .CREATE_FILE, .{}); + 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); @@ -3606,9 +3909,9 @@ test "Fat32FS.getDirCluster - sub dir" { defer test_fat32_image.close(); var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image); - defer test_fs.destroy(); + defer test_fs.destroy() catch unreachable; - var test_node_1 = try test_fs.createNode(5, 0, .CREATE_DIR, .{}); + 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); @@ -3620,9 +3923,9 @@ test "Fat32FS.getDirCluster - not opened dir" { defer test_fat32_image.close(); var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image); - defer test_fs.destroy(); + defer test_fs.destroy() catch unreachable; - var test_node_1 = try test_fs.createNode(5, 0, .CREATE_DIR, .{}); + var test_node_1 = try test_fs.createNode(5, 0, 0, 0, .CREATE_DIR); const elem = test_fs.opened_files.remove(test_node_1).?.value; std.testing.allocator.destroy(elem); @@ -3635,9 +3938,9 @@ test "Fat32FS.openImpl - entry iterator failed init" { defer test_fat32_image.close(); var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image); - defer test_fs.destroy(); + defer test_fs.destroy() catch unreachable; - var test_node_1 = try test_fs.createNode(5, 0, .CREATE_DIR, .{}); + 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); @@ -3652,7 +3955,7 @@ test "Fat32FS.openImpl - entry iterator failed next" { defer test_fat32_image.close(); var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image); - defer test_fs.destroy(); + defer test_fs.destroy() catch unreachable; var fa = std.testing.FailingAllocator.init(std.testing.allocator, 2); const allocator = &fa.allocator; @@ -3666,7 +3969,7 @@ test "Fat32FS.openImpl - entry iterator failed 2nd next" { defer test_fat32_image.close(); var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image); - defer test_fs.destroy(); + defer test_fs.destroy() catch unreachable; var fa = std.testing.FailingAllocator.init(std.testing.allocator, 3); const allocator = &fa.allocator; @@ -3680,7 +3983,7 @@ test "Fat32FS.openImpl - match short name" { defer test_fat32_image.close(); var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image); - defer test_fs.destroy(); + 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, "INSANE~1.TXT"); defer file_node.File.close(); @@ -3691,7 +3994,7 @@ test "Fat32FS.openImpl - match long name" { defer test_fat32_image.close(); var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image); - defer test_fs.destroy(); + 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"); defer file_node.File.close(); @@ -3702,9 +4005,9 @@ test "Fat32FS.openImpl - no match" { defer test_fat32_image.close(); var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image); - defer test_fs.destroy(); + defer test_fs.destroy() catch unreachable; - var test_node_1 = try test_fs.createNode(5, 0, .CREATE_DIR, .{}); + var test_node_1 = try test_fs.createNode(5, 0, 0, 0, .CREATE_DIR); defer test_node_1.Dir.close(); expectError(vfs.Error.NoSuchFileOrDir, Fat32FS(@TypeOf(test_fat32_image)).openImpl(test_fs.fs, &test_node_1.Dir, "file.txt")); @@ -3716,15 +4019,15 @@ test "Fat32FS.open - no create - hand crafted" { var stream = &std.io.fixedBufferStream(test_file_buf[0..]); - try mkfat32.Fat32.make(.{}, stream, true); + 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(); + 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, 0xFF, + 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, @@ -3736,7 +4039,7 @@ test "Fat32FS.open - no create - hand crafted" { 0xFE, 0x50, 0x00, 0x00, 0x00, 0x00, 0x6E, 0xA9, 0xFE, 0x50, 0x08, 0x00, 0x13, 0x00, 0x00, 0x00, // Long entry 2 0x42, 0x2E, 0x00, 0x74, 0x00, 0x78, 0x00, 0x74, 0x00, 0x00, 0x00, 0x0F, 0x00, 0xE9, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 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, @@ -3750,7 +4053,7 @@ test "Fat32FS.open - no create - hand crafted" { try test_fs.stream.seekableStream().seekTo(sector * test_fs.fat_config.bytes_per_sector); try test_fs.stream.writer().writeAll(entry_buff[0..]); - vfs.setRoot(test_fs.root_node.node); + try vfs.setRoot(test_fs.root_node.node); const file = try vfs.openFile("/ramdisk_test1.txt", .NO_CREATION); defer file.close(); @@ -3761,41 +4064,144 @@ test "Fat32FS.open - no create - hand crafted" { 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(); + defer test_fs.destroy() catch unreachable; - vfs.setRoot(test_fs.root_node.node); + 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(); - - var it = test_files.iterate(); - while (try it.next()) |file| { - // Need to add a '/' at the beginning - var file_name = try std.testing.allocator.alloc(u8, file.name.len + 1); - defer std.testing.allocator.free(file_name); - file_name[0] = '/'; - std.mem.copy(u8, file_name[1..], file.name); - const open_file = try vfs.openFile(file_name, .NO_CREATION); - defer open_file.close(); - } + try testOpenRec(&test_fs.root_node.node.Dir, "test/fat32/test_files"); } test "Fat32FS.open - create file" { - // TODO: Once the open and write PR is done + 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 + 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..]); + expectEqual(read, 0); + expectEqualSlices(u8, buff[0..], &[_]u8{0xAA} ** 512); } test "Fat32FS.open - create directory" { - // TODO: Once the open and write PR is done + 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 + 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" { - // TODO: Once the open and write PR is done + 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); + + 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" { @@ -3803,7 +4209,7 @@ test "Fat32FS.read - not opened" { defer test_fat32_image.close(); var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image); - defer test_fs.destroy(); + defer test_fs.destroy() catch unreachable; // Craft a node var node = try std.testing.allocator.create(vfs.Node); @@ -3818,9 +4224,9 @@ test "Fat32FS.read - cluster iterator init fail" { defer test_fat32_image.close(); var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image); - defer test_fs.destroy(); + defer test_fs.destroy() catch unreachable; - var test_node = try test_fs.createNode(5, 16, .CREATE_FILE, .{}); + 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); @@ -3836,9 +4242,9 @@ test "Fat32FS.read - buffer smaller than file" { defer test_fat32_image.close(); var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image); - defer test_fs.destroy(); + defer test_fs.destroy() catch unreachable; - vfs.setRoot(test_fs.root_node.node); + try vfs.setRoot(test_fs.root_node.node); const test_node = try vfs.openFile("/short.txt", .NO_CREATION); defer test_node.close(); @@ -3853,9 +4259,9 @@ test "Fat32FS.read - buffer bigger than file" { defer test_fat32_image.close(); var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image); - defer test_fs.destroy(); + defer test_fs.destroy() catch unreachable; - vfs.setRoot(test_fs.root_node.node); + try vfs.setRoot(test_fs.root_node.node); const test_node = try vfs.openFile("/short.txt", .NO_CREATION); defer test_node.close(); @@ -3872,9 +4278,9 @@ test "Fat32FS.read - large" { defer test_fat32_image.close(); var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image); - defer test_fs.destroy(); + defer test_fs.destroy() catch unreachable; - vfs.setRoot(test_fs.root_node.node); + try vfs.setRoot(test_fs.root_node.node); const test_node = try vfs.openFile("/large_file.txt", .NO_CREATION); defer test_node.close(); @@ -3887,43 +4293,1800 @@ test "Fat32FS.read - large" { 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..]); + expectEqualSlices(u8, buff[0..], large_file_content[0..]); + 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 + expectEqual(file.name.len, read); + 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(); + defer test_fs.destroy() catch unreachable; - vfs.setRoot(test_fs.root_node.node); + 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(); - var it = test_files.iterate(); - while (try it.next()) |file| { - // Have tested the large file - if (std.mem.eql(u8, file.name, "large_file.txt")) { - continue; - } - // Need to add a '/' at the beginning - var file_name = try std.testing.allocator.alloc(u8, file.name.len + 1); - defer std.testing.allocator.free(file_name); - file_name[0] = '/'; - std.mem.copy(u8, file_name[1..], file.name); - const open_file = try vfs.openFile(file_name, .NO_CREATION); - defer open_file.close(); + try testReadRec(&test_fs.root_node.node.Dir, "test/fat32/test_files", false); +} - // Big enough - var buff = [_]u8{0xAA} ** 256; - const read = try open_file.read(buff[0..]); +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; - // The file content is the same as the file name - expectEqual(file_name.len - 1, read); - expectEqualSlices(u8, buff[0..read], file_name[1..]); + 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); + expectEqual(cluster, 6); + // check the FAT where the update would happen + backup FAT + expectEqualSlices(u8, fat_buff_stream[24..28], &[_]u8{ 0xFF, 0xFF, 0xFF, 0x0F }); + 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); + expectEqual(cluster, 10); + // check the FAT where the update would happen + backup FAT + expectEqualSlices(u8, fat_buff_stream[40..44], &[_]u8{ 0xFF, 0xFF, 0xFF, 0x0F }); + 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); + expectEqual(cluster, 10); + // check the FAT where the update would happen + backup FAT + expectEqualSlices(u8, fat_buff_stream[24..28], &[_]u8{ 0xFF, 0xFF, 0xFF, 0x0F }); + 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; + + 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); + expectEqual(cluster, 6); + expectEqual(test_fs.fat_config.number_free_clusters, 9); + expectEqual(test_fs.fat_config.next_free_cluster, 7); + expectEqual(buff_stream[488], 9); + expectEqual(buff_stream[492], 7); + expectEqual(buff_stream[1000], 9); + 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); + expectEqual(cluster, 6); + // check the FAT where the update would happen + backup FAT + expectEqualSlices(u8, fat_buff_stream[20..28], &[_]u8{ 0x06, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x0F }); + 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{}); + 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); + expectEqualSlices(u16, expected[0..], actual); } } -test "Fat32FS.write" { - // TODO: Once the write PR is done +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", + [_]u8{0x10} ++ "file.txt", + [_]u8{0x7F} ++ "file.txt", + "\u{12345}file.txt", + }; + + var stream = &std.io.fixedBufferStream(&[_]u8{}); + for (name_cases) |case| { + 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); + 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{}); + expectEqual(Fat32FS(@TypeOf(stream)).isValidSFNChar(' '), null); + expectEqual(Fat32FS(@TypeOf(stream)).isValidSFNChar('€'), null); + expectEqual(Fat32FS(@TypeOf(stream)).isValidSFNChar('+'), null); + expectEqual(Fat32FS(@TypeOf(stream)).isValidSFNChar(','), null); + expectEqual(Fat32FS(@TypeOf(stream)).isValidSFNChar(';'), null); + expectEqual(Fat32FS(@TypeOf(stream)).isValidSFNChar('='), null); + expectEqual(Fat32FS(@TypeOf(stream)).isValidSFNChar('['), null); + expectEqual(Fat32FS(@TypeOf(stream)).isValidSFNChar(']'), null); + + expectEqual(Fat32FS(@TypeOf(stream)).isValidSFNChar('α'), 0xE0); + 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{}); + 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{}); + 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{}); + 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{}); + 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{}); + 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{}); + 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); + 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); + 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); + 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); + 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); + 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); + 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{}); + 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); + + 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 }, + }; + + 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); + + 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'}, + }, + }; + + expectEqual(entries[0], expected[0]); + 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); + + 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| { + 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, + }; + 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); + 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); + 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); + expectEqualSlices(u8, expected_bytes[0..], buff_stream[96..]); + expectEqualSlices(u8, buff_stream[8..16], &[_]u8{ 0x03, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x0F }); + 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); + + 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); + 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..]); + expectEqualSlices(u8, buff_stream[64..96], temp_buf[0..]); + initBytes(ShortName, expected_short_entry, temp_buf[0..]); + expectEqualSlices(u8, buff_stream[128..], temp_buf[0..]); + + // FAT + expectEqualSlices(u8, buff_stream[8..12], &[_]u8{ 0x04, 0x00, 0x00, 0x00 }); + expectEqualSlices(u8, buff_stream[12..16], &[_]u8{ 0xFF, 0xFF, 0xFF, 0x0F }); + 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..]); + expectEqualSlices(u8, buff_stream[64..96], temp_buf[0..]); + initBytes(ShortName, expected_short_entry, temp_buf[0..]); + expectEqualSlices(u8, buff_stream[128..], temp_buf[0..]); + + // FAT + expectEqualSlices(u8, buff_stream[8..12], &[_]u8{ 0x04, 0x00, 0x00, 0x00 }); + expectEqualSlices(u8, buff_stream[12..16], &[_]u8{ 0xFF, 0xFF, 0xFF, 0x0F }); + 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..]); + expectEqualSlices(u8, buff_stream[128..160], temp_buf[0..]); + initBytes(ShortName, expected_short_entry, temp_buf[0..]); + expectEqualSlices(u8, buff_stream[160..], temp_buf[0..]); + + // FAT + expectEqualSlices(u8, buff_stream[8..12], &[_]u8{ 0x04, 0x00, 0x00, 0x00 }); + expectEqualSlices(u8, buff_stream[12..16], &[_]u8{ 0xFF, 0xFF, 0xFF, 0x0F }); + expectEqualSlices(u8, buff_stream[16..20], &[_]u8{ 0x05, 0x00, 0x00, 0x00 }); + 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..]); + expectEqualSlices(u8, buff_stream[160..192], temp_buf[0..]); + initBytes(ShortName, expected_short_entry, temp_buf[0..]); + expectEqualSlices(u8, buff_stream[256..288], temp_buf[0..]); + + // FAT + expectEqualSlices(u8, buff_stream[8..12], &[_]u8{ 0x04, 0x00, 0x00, 0x00 }); + expectEqualSlices(u8, buff_stream[12..16], &[_]u8{ 0xFF, 0xFF, 0xFF, 0x0F }); + 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..]); + expectEqual(written, text.len); + + var read_buf1: [text.len * 2]u8 = undefined; + const read1 = try file.read(read_buf1[0..]); + expectEqual(read1, text.len); + 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..]); + + expectEqual(read2, text.len); + 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)).?; + expectEqual(open_info1.cluster, 3); + expectEqual(open_info1.size, 0); + expectEqual(open_info1.entry_cluster, 2); + expectEqual(open_info1.entry_offset, 60); + + const fat_offset = test_fs.fat_config.reserved_sectors * test_fs.fat_config.bytes_per_sector + 12; + 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..]); + 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 }; + 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..]); + expectEqual(read1, text.len); + 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)).?; + expectEqual(open_info2.cluster, 3); + expectEqual(open_info2.size, text.len); + expectEqual(open_info2.entry_cluster, 2); + expectEqual(open_info2.entry_offset, 60); + + var read_buf2: [text.len * 2]u8 = undefined; + const read2 = try read_file.read(read_buf2[0..]); + + expectEqual(read2, text.len); + 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(); + + expectError(error.Unexpected, file.write(text[0..])); + + const offset = test_fs.fat_config.clusterToSector(3) * test_fs.fat_config.bytes_per_sector; + 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); + + const read = try test_fat32_image.reader().readAll(test_file_buf[0..]); + const stream = &std.io.fixedBufferStream(test_file_buf[0..]); + + // BadMBRMagic + test_file_buf[510] = 0x00; + expectError(error.BadMBRMagic, initialiseFAT32(std.testing.allocator, stream)); + test_file_buf[510] = 0x55; + + test_file_buf[511] = 0x00; + expectError(error.BadMBRMagic, initialiseFAT32(std.testing.allocator, stream)); + test_file_buf[511] = 0xAA; + + // BadRootCluster + // Little endian, so just eed to set the upper bytes + test_file_buf[44] = 0; + expectError(error.BadRootCluster, initialiseFAT32(std.testing.allocator, stream)); + + test_file_buf[44] = 1; + expectError(error.BadRootCluster, initialiseFAT32(std.testing.allocator, stream)); + test_file_buf[44] = 2; + + // BadFATCount + test_file_buf[16] = 0; + expectError(error.BadFATCount, initialiseFAT32(std.testing.allocator, stream)); + + test_file_buf[16] = 1; + expectError(error.BadFATCount, initialiseFAT32(std.testing.allocator, stream)); + + test_file_buf[16] = 10; + expectError(error.BadFATCount, initialiseFAT32(std.testing.allocator, stream)); + test_file_buf[16] = 2; + + // NotMirror + test_file_buf[40] = 1; + expectError(error.NotMirror, initialiseFAT32(std.testing.allocator, stream)); + + test_file_buf[40] = 10; + expectError(error.NotMirror, initialiseFAT32(std.testing.allocator, stream)); + test_file_buf[40] = 0; + + // BadMedia + test_file_buf[21] = 0xF0; + expectError(error.BadMedia, initialiseFAT32(std.testing.allocator, stream)); + test_file_buf[21] = 0xF8; + + // BadFat32 + test_file_buf[17] = 10; + expectError(error.BadFat32, initialiseFAT32(std.testing.allocator, stream)); + test_file_buf[17] = 0; + + test_file_buf[19] = 10; + expectError(error.BadFat32, initialiseFAT32(std.testing.allocator, stream)); + test_file_buf[19] = 0; + + test_file_buf[22] = 10; + expectError(error.BadFat32, initialiseFAT32(std.testing.allocator, stream)); + test_file_buf[22] = 0; + + // BadSignature + test_file_buf[66] = 0x28; + expectError(error.BadSignature, initialiseFAT32(std.testing.allocator, stream)); + test_file_buf[66] = 0x29; + + // BadFSType + // Change from FAT32 to FAT16 + test_file_buf[85] = '1'; + test_file_buf[86] = '6'; + expectError(error.BadFSType, initialiseFAT32(std.testing.allocator, stream)); + test_file_buf[85] = '3'; + test_file_buf[86] = '2'; + + // Test the bad reads + // Boot sector + expectError(error.BadRead, initialiseFAT32(std.testing.allocator, &std.io.fixedBufferStream(test_file_buf[0..510]))); + // FSInfo (we have one) + expectError(error.BadRead, initialiseFAT32(std.testing.allocator, &std.io.fixedBufferStream(test_file_buf[0 .. 512 + 100]))); + // FAT + expectError(error.BadRead, initialiseFAT32(std.testing.allocator, &std.io.fixedBufferStream(test_file_buf[0 .. (32 * 512 + 4) + 1]))); +} + +test "Fat32FS.init free memory" { + const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{}); + defer test_fat32_image.close(); + + const allocations: usize = 5; + var i: usize = 0; + while (i < allocations) : (i += 1) { + var fa = std.testing.FailingAllocator.init(std.testing.allocator, i); + expectError(error.OutOfMemory, initialiseFAT32(&fa.allocator, test_fat32_image)); + } +} + +test "Fat32FS.init FATConfig expected" { + const test_fat32_image = try std.fs.cwd().openFile("test/fat32/test_fat32.img", .{}); + defer test_fat32_image.close(); + + var test_fs = try initialiseFAT32(std.testing.allocator, test_fat32_image); + defer test_fs.destroy() 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 = 65471, + .next_free_cluster = 55, + .cluster_end_marker = 0x0FFFFFFF, + }; + + 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); + + const read = try test_fat32_image.reader().readAll(test_file_buf[0..]); + const stream = &std.io.fixedBufferStream(test_file_buf[0..]); + + // No FSInfo + { + // Force no FSInfo + test_file_buf[48] = 0x00; + + var test_fs = try initialiseFAT32(std.testing.allocator, stream); + defer test_fs.destroy() 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, + }; + + 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, + }; + + 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 = 55, + .cluster_end_marker = 0x0FFFFFFF, + }; + + expectEqual(test_fs.fat_config, expected); + } } diff --git a/src/kernel/filesystem/initrd.zig b/src/kernel/filesystem/initrd.zig index 5cf07e1..1c44a6c 100644 --- a/src/kernel/filesystem/initrd.zig +++ b/src/kernel/filesystem/initrd.zig @@ -371,7 +371,7 @@ test "open valid file" { var fs = try InitrdFS.init(&initrd_stream, std.testing.allocator); defer fs.deinit(); - vfs.setRoot(fs.root_node); + try vfs.setRoot(fs.root_node); var file1 = try vfs.openFile("/test1.txt", .NO_CREATION); defer file1.close(); @@ -401,7 +401,7 @@ test "open fail with invalid flags" { var fs = try InitrdFS.init(&initrd_stream, std.testing.allocator); defer fs.deinit(); - vfs.setRoot(fs.root_node); + try vfs.setRoot(fs.root_node); expectError(error.InvalidFlags, vfs.openFile("/text10.txt", .CREATE_DIR)); expectError(error.InvalidFlags, vfs.openFile("/text10.txt", .CREATE_FILE)); @@ -428,7 +428,7 @@ test "open fail with NoSuchFileOrDir" { var fs = try InitrdFS.init(&initrd_stream, std.testing.allocator); defer fs.deinit(); - vfs.setRoot(fs.root_node); + try vfs.setRoot(fs.root_node); expectError(error.NoSuchFileOrDir, vfs.openFile("/text10.txt", .NO_CREATION)); expectError(error.NoSuchFileOrDir, vfs.openDir("/temp/", .NO_CREATION)); } @@ -443,7 +443,7 @@ test "open a file, out of memory" { var fs = try InitrdFS.init(&initrd_stream, &fa.allocator); defer fs.deinit(); - vfs.setRoot(fs.root_node); + try vfs.setRoot(fs.root_node); expectError(error.OutOfMemory, vfs.openFile("/test1.txt", .NO_CREATION)); } @@ -456,7 +456,7 @@ test "open two of the same file" { var fs = try InitrdFS.init(&initrd_stream, std.testing.allocator); defer fs.deinit(); - vfs.setRoot(fs.root_node); + try vfs.setRoot(fs.root_node); const file1 = try vfs.openFile("/test1.txt", .NO_CREATION); defer file1.close(); @@ -482,7 +482,7 @@ test "close a file" { var fs = try InitrdFS.init(&initrd_stream, std.testing.allocator); defer fs.deinit(); - vfs.setRoot(fs.root_node); + try vfs.setRoot(fs.root_node); var file1 = try vfs.openFile("/test1.txt", .NO_CREATION); @@ -514,7 +514,7 @@ test "close a non-opened file" { var fs = try InitrdFS.init(&initrd_stream, std.testing.allocator); defer fs.deinit(); - vfs.setRoot(fs.root_node); + try vfs.setRoot(fs.root_node); // Open a valid file var file1 = try vfs.openFile("/test1.txt", .NO_CREATION); @@ -541,7 +541,7 @@ test "read a file" { var fs = try InitrdFS.init(&initrd_stream, std.testing.allocator); defer fs.deinit(); - vfs.setRoot(fs.root_node); + try vfs.setRoot(fs.root_node); var file1 = try vfs.openFile("/test1.txt", .NO_CREATION); defer file1.close(); @@ -565,7 +565,7 @@ test "read a file, invalid/not opened/crafted *const Node" { var fs = try InitrdFS.init(&initrd_stream, std.testing.allocator); defer fs.deinit(); - vfs.setRoot(fs.root_node); + try vfs.setRoot(fs.root_node); // Open a valid file var file1 = try vfs.openFile("/test1.txt", .NO_CREATION); @@ -594,7 +594,7 @@ test "write does nothing" { var fs = try InitrdFS.init(&initrd_stream, std.testing.allocator); defer fs.deinit(); - vfs.setRoot(fs.root_node); + try vfs.setRoot(fs.root_node); // Open a valid file var file1 = try vfs.openFile("/test1.txt", .NO_CREATION); @@ -663,7 +663,9 @@ fn rt_openReadClose(allocator: *Allocator) void { fn runtimeTests(rd_fs: *InitrdFS) void { // There will be test files provided for the runtime tests // Need to init the VFS. This will be overridden after the tests. - vfs.setRoot(rd_fs.root_node); + vfs.setRoot(rd_fs.root_node) catch |e| { + panic(@errorReturnTrace(), "Ramdisk root node isn't a directory node: {}\n", .{e}); + }; rt_openReadClose(rd_fs.allocator); if (rd_fs.opened_files.count() != 0) { panic(@errorReturnTrace(), "FAILURE: Didn't close all files\n", .{}); diff --git a/src/kernel/filesystem/vfs.zig b/src/kernel/filesystem/vfs.zig index d8e9458..23a333b 100644 --- a/src/kernel/filesystem/vfs.zig +++ b/src/kernel/filesystem/vfs.zig @@ -488,7 +488,7 @@ pub fn openDir(path: []const u8, flags: OpenFlags) (Allocator.Error || Error)!*D file.close(); break :blk Error.IsAFile; }, - // We instructed open to folow symlinks above, so this is impossible + // We instructed open to follow symlinks above, so this is impossible .Symlink => unreachable, .Dir => &node.Dir, }; @@ -499,7 +499,7 @@ pub fn openDir(path: []const u8, flags: OpenFlags) (Allocator.Error || Error)!*D /// /// Arguments: /// IN path: []const u8 - The path to open. Must be absolute (see isAbsolute) -/// IN tareget: ?[]const u8 - The target to use when creating the symlink. Can be null if .NO_CREATION is used as the open flag +/// IN target: ?[]const u8 - The target to use when creating the symlink. Can be null if .NO_CREATION is used as the open flag /// IN flags: OpenFlags - The flags specifying if this node should be created if it doesn't exist. Cannot be CREATE_FILE /// /// Return: []const u8 @@ -552,7 +552,13 @@ pub fn isAbsolute(path: []const u8) bool { /// Arguments: /// IN node: *Node - The node to initialise the root node. /// -pub fn setRoot(node: *Node) void { +/// Error: Error +/// Error.NotADirectory - The node isn't a directory node. +/// +pub fn setRoot(node: *Node) Error!void { + if (!node.isDir()) { + return Error.NotADirectory; + } root = node; } @@ -972,8 +978,8 @@ test "read" { testing.expectEqual(test_link, "/foo.txt"); var link_file = try openFile("/link", .NO_CREATION); { - const length = try link_file.read(buffer[0..0]); - testing.expect(std.mem.eql(u8, str[0..0], buffer[0..length])); + const length = try link_file.read(buffer[0..]); + testing.expect(std.mem.eql(u8, str[0..], buffer[0..length])); } } diff --git a/src/kernel/kmain.zig b/src/kernel/kmain.zig index ebbfe36..95b5766 100644 --- a/src/kernel/kmain.zig +++ b/src/kernel/kmain.zig @@ -129,7 +129,9 @@ export fn kmain(boot_payload: arch.BootPayload) void { }; // Need to init the vfs after the ramdisk as we need the root node from the ramdisk filesystem - vfs.setRoot(ramdisk_filesystem.root_node); + vfs.setRoot(ramdisk_filesystem.root_node) catch |e| { + panic_root.panic(@errorReturnTrace(), "Ramdisk root node isn't a directory node: {}\n", .{e}); + }; } scheduler.init(&kernel_heap.allocator, &mem_profile) catch |e| { diff --git a/test/fat32/test_files/file~a.txt b/test/fat32/test_files/file~a.txt new file mode 100644 index 0000000..1a4eac5 --- /dev/null +++ b/test/fat32/test_files/file~a.txt @@ -0,0 +1 @@ +file~a.txt \ No newline at end of file diff --git a/test/fat32/test_files/folder1/file1.txt b/test/fat32/test_files/folder1/file1.txt new file mode 100644 index 0000000..39cd576 --- /dev/null +++ b/test/fat32/test_files/folder1/file1.txt @@ -0,0 +1 @@ +file1.txt \ No newline at end of file diff --git a/test/fat32/test_files/folder1/folder2/file2.txt b/test/fat32/test_files/folder1/folder2/file2.txt new file mode 100644 index 0000000..c3ee11c --- /dev/null +++ b/test/fat32/test_files/folder1/folder2/file2.txt @@ -0,0 +1 @@ +file2.txt \ No newline at end of file diff --git a/test/fat32/test_files/folder1/folder2/folder3/file3.txt b/test/fat32/test_files/folder1/folder2/folder3/file3.txt new file mode 100644 index 0000000..49081a8 --- /dev/null +++ b/test/fat32/test_files/folder1/folder2/folder3/file3.txt @@ -0,0 +1 @@ +file3.txt \ No newline at end of file diff --git a/test/fat32/test_files/folder1/folder2/folder3/folder4/file4.txt b/test/fat32/test_files/folder1/folder2/folder3/folder4/file4.txt new file mode 100644 index 0000000..7fd9270 --- /dev/null +++ b/test/fat32/test_files/folder1/folder2/folder3/folder4/file4.txt @@ -0,0 +1 @@ +file4.txt \ No newline at end of file diff --git a/test/fat32/test_files/folder1/folder2/folder3/folder4/folder5/file5.txt b/test/fat32/test_files/folder1/folder2/folder3/folder4/folder5/file5.txt new file mode 100644 index 0000000..f724f5f --- /dev/null +++ b/test/fat32/test_files/folder1/folder2/folder3/folder4/folder5/file5.txt @@ -0,0 +1 @@ +file5.txt \ No newline at end of file diff --git a/test/fat32/test_files/folder1/folder2/folder3/folder4/folder5/folder6/file6.txt b/test/fat32/test_files/folder1/folder2/folder3/folder4/folder5/folder6/file6.txt new file mode 100644 index 0000000..afde385 --- /dev/null +++ b/test/fat32/test_files/folder1/folder2/folder3/folder4/folder5/folder6/file6.txt @@ -0,0 +1 @@ +file6.txt \ No newline at end of file diff --git a/test/fat32/test_files/folder1/folder2/folder3/folder4/folder5/folder6/folder7/file7.txt b/test/fat32/test_files/folder1/folder2/folder3/folder4/folder5/folder6/folder7/file7.txt new file mode 100644 index 0000000..1d40128 --- /dev/null +++ b/test/fat32/test_files/folder1/folder2/folder3/folder4/folder5/folder6/folder7/file7.txt @@ -0,0 +1 @@ +file7.txt \ No newline at end of file diff --git a/test/fat32/test_files/folder1/folder2/folder3/folder4/folder5/folder6/folder7/folder8/file8.txt b/test/fat32/test_files/folder1/folder2/folder3/folder4/folder5/folder6/folder7/folder8/file8.txt new file mode 100644 index 0000000..215ed79 --- /dev/null +++ b/test/fat32/test_files/folder1/folder2/folder3/folder4/folder5/folder6/folder7/folder8/file8.txt @@ -0,0 +1 @@ +file8.txt \ No newline at end of file diff --git a/test/fat32/test_files/folder1/folder2/folder3/folder4/folder5/folder6/folder7/folder8/folder9/file9.txt b/test/fat32/test_files/folder1/folder2/folder3/folder4/folder5/folder6/folder7/folder8/folder9/file9.txt new file mode 100644 index 0000000..258f30f --- /dev/null +++ b/test/fat32/test_files/folder1/folder2/folder3/folder4/folder5/folder6/folder7/folder8/folder9/file9.txt @@ -0,0 +1 @@ +file9.txt \ No newline at end of file diff --git a/test/mock/kernel/arch_mock.zig b/test/mock/kernel/arch_mock.zig index e711794..b228b91 100644 --- a/test/mock/kernel/arch_mock.zig +++ b/test/mock/kernel/arch_mock.zig @@ -10,10 +10,19 @@ const paging = @import("paging_mock.zig"); const Serial = @import("../../../src/kernel/serial.zig").Serial; const TTY = @import("../../../src/kernel/tty.zig").TTY; const Keyboard = @import("../../../src/kernel/keyboard.zig").Keyboard; - -pub const task = @import("../../../src/kernel/task.zig"); +const task = @import("../../../src/kernel/task.zig"); pub const Device = pci.PciDeviceInfo; +pub const DateTime = struct { + second: u32, + minute: u32, + hour: u32, + day: u32, + month: u32, + year: u32, + century: u32, + day_of_week: u32, +}; const mock_framework = @import("mock_framework.zig"); pub const initTest = mock_framework.initTest; @@ -151,10 +160,25 @@ pub fn getDevices(allocator: *Allocator) Allocator.Error![]Device { return &[_]Device{}; } +pub fn getDateTime() DateTime { + // TODO: Use the std lib std.time.timestamp() and convert + // Hard code 12:12:13 12/12/12 for testing + return .{ + .second = 13, + .minute = 12, + .hour = 12, + .day = 12, + .month = 12, + .year = 2012, + .century = 2000, + .day_of_week = 4, + }; +} + pub fn init(mem_profile: *const MemProfile) void { // I'll get back to this as this doesn't effect the current testing. // When I come on to the mem.zig testing, I'll fix :) - //return mock_framework.performAction("init", void, mem_profile, allocator); + //return mock_framework.performAction("init", void, mem_profile); } // User defined mocked functions