4afecd6508
Added finding the next free cluster Added name to long name Added long name to short name Added tests for above Added createLongNameEntry + tests Moved tests to bottom Added createShortNameEntry + test Used the RTC for the date and time for the created short entry Tidied createEntries A bit of refactor No symlinks for FAT32 findNextFreeCluster updates FAT Reordered tests to better follow the FAT32FS code FAT32 has no support for symlinks, so removed code around this. Removed open_args from createNode as it doesn't need it Added writeEntries + tests Write the short and long entries to disk findNextFreeCluster update cluster chain with parent cluster Added FAT32 write + tests This Added the ability to create files and directories and write to files. Added location of the short dir entry for the file so can update the size of the file on disk Added folders to the test FAT32 directory. Also fixed minor bug in mkfat32 Added check for destroying the filesystem Fixed error message for cluster size Simpler if condition 0x0FFFFFFF => 0xFFFFFFFF Spelling Fixed test
605 lines
28 KiB
Zig
605 lines
28 KiB
Zig
const std = @import("std");
|
|
|
|
// This is the assembly for the FAT bootleader.
|
|
// [bits 16]
|
|
// [org 0x7C00]
|
|
//
|
|
// jmp short _start
|
|
// nop
|
|
//
|
|
// times 87 db 0xAA
|
|
//
|
|
// _start:
|
|
// jmp long 0x0000:start_16bit
|
|
//
|
|
// start_16bit:
|
|
// cli
|
|
// mov ax, cs
|
|
// mov ds, ax
|
|
// mov es, ax
|
|
// mov ss, ax
|
|
// mov sp, 0x7C00
|
|
// lea si, [message]
|
|
// .print_string_with_new_line:
|
|
// mov ah, 0x0E
|
|
// xor bx, bx
|
|
// .print_string_loop:
|
|
// lodsb
|
|
// cmp al, 0
|
|
// je .print_string_done
|
|
// int 0x10
|
|
// jmp short .print_string_loop
|
|
// .print_string_done:
|
|
// mov al, 0x0A
|
|
// int 0x10
|
|
// mov al, 0x0D
|
|
// int 0x10
|
|
//
|
|
// .reboot:
|
|
// xor ah, ah
|
|
// int 0x16
|
|
// int 0x19
|
|
//
|
|
// .loop_forever:
|
|
// hlt
|
|
// jmp .loop_forever
|
|
|
|
// message db "This is not a bootable disk. Please insert a bootable floppy and press any key to try again", 0
|
|
// times 510 - ($ - $$) db 0
|
|
// dw 0xAA55
|
|
|
|
/// A FAT32 static structure for creating a empty FAT32 image. This contains helper functions
|
|
/// for creating a empty FAT32 image.
|
|
/// For more information, see: https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system
|
|
pub const Fat32 = struct {
|
|
/// The FAT32 boot sector header (without the jmp and boot code) including the extended
|
|
/// parameter block for FAT32 with the extended signature 0x29.
|
|
const Header = struct {
|
|
/// The OEM.
|
|
oem: [8]u8 = "ZystemOS".*,
|
|
|
|
/// The number of bytes per sector. This needs to be initialised by the user or use the
|
|
/// default value at runtime. Use `getDefaultSectorSize` for the default.
|
|
bytes_per_sector: u16,
|
|
|
|
/// The number of sectors per cluster. This needs to be initialised by the user or use the
|
|
/// default value at runtime. Use `getDefaultSectorsPerCluster` for the default.
|
|
sectors_per_cluster: u8,
|
|
|
|
/// The number of reserved sectors at the beginning of the image. This is where the fat
|
|
/// header, FSInfo and FAT is stored. The default is 32 sectors.
|
|
/// TODO: Investigate if this can be reduced if not all sectors are needed.
|
|
reserved_sectors: u16 = getReservedSectors(),
|
|
|
|
/// The number of FAT's. This is always 2.
|
|
fat_count: u8 = 2,
|
|
|
|
/// The size in bytes of the root directory. This is only used by FAT12 and FAT16, so is
|
|
/// always 0 for FAT32.
|
|
zero_root_directory_size: u16 = 0,
|
|
|
|
/// The total number of sectors. This is only used for FAT12 and FAT16, so is always 0 for
|
|
/// FAT32.
|
|
zero_total_sectors_16: u16 = 0,
|
|
|
|
/// The media type. This is used to identify the type of media this image is. As all FAT's
|
|
/// that are created as a fixed disk, non of that old school floppy, this is always 0xF8.
|
|
media_descriptor_type: u8 = 0xF8,
|
|
|
|
/// The total number of sectors of the FAT. This is only used for FAT12 and FAT16, so
|
|
/// always 0 for FAT32.
|
|
zero_sectors_per_fat: u16 = 0,
|
|
|
|
/// The number of sectors per track for Int 13h. This isn't really needed as we are
|
|
/// creating a fixed disk. An example used 63.
|
|
sectors_per_track: u16 = 63,
|
|
|
|
/// The number of heads for Int 13h. This isn't really needed as we are creating a fixed
|
|
/// disk. An example used 255.
|
|
head_count: u16 = 255,
|
|
|
|
/// The number of hidden sectors. As there is no support for partitions, this is set ot zero.
|
|
hidden_sectors: u32 = 0,
|
|
|
|
/// The total number of sectors of the image. This is determined at runtime by the size of
|
|
/// image and the sector size.
|
|
total_sectors: u32,
|
|
|
|
/// The number of sectors the FAT takes up. This is set based on the size of the image.
|
|
sectors_per_fat: u32,
|
|
|
|
/// Mirror flags. If bit 7 is set, then bits 0-3 represent the number of active FAT entries
|
|
/// (zero based) and if clear, all FAT's are mirrored. Always mirror, so zero.
|
|
mirror_flags: u16 = 0x0000,
|
|
|
|
/// The version. This is usually always zero (0.0). If version is 1.2, this will be stored
|
|
/// as 0x0201.
|
|
version_number: u16 = 0x0000,
|
|
|
|
/// The start cluster of the root directory. This is usually always 2 unless there is a bad
|
|
/// sector, 0 or 1 are invalid.
|
|
root_directory_cluster: u32 = 2,
|
|
|
|
/// The sector where is the FS information sector is located. A value of 0x0000 or 0xFFFF
|
|
/// indicates that there isn't a FSInfo structure. A value of 0x0000 should not be treated
|
|
/// as a valid FSInfo sector. Without a FS Information Sector, the minimum allowed logical
|
|
/// sector size of FAT32 volumes can be reduced down to 128 bytes for special purposes.
|
|
/// This is usually sector 1.
|
|
fsinfo_sector: u16 = 1,
|
|
|
|
/// The sector for the backup boot record where the first 3 sectors are copied. A value of
|
|
/// 0x000 or 0xFFFF indicate there is no backup. This usually 6.
|
|
backup_boot_sector: u16 = 6,
|
|
|
|
/// Reserved,. All zero.
|
|
reserved0: u96 = 0x000000000000,
|
|
|
|
/// The physical drive number for Int 13h in the BIOS. 0x80 is for fixed disks, but as this
|
|
/// is used for bootloaders, this isn't really needed.
|
|
drive_number: u8 = 0x80,
|
|
|
|
/// Reserved. All zero.
|
|
reserved1: u8 = 0x00,
|
|
|
|
/// The extended boot signature. 0x29, can be 0x28, but will only accept 0x29. If 0x28,
|
|
/// then the below (serial_number, volume_label and filesystem_type) will be unavailable.
|
|
signature: u8 = 0x29,
|
|
|
|
/// The serial number of the FAT image at format. This is a function of the current
|
|
/// timestamp of creation.
|
|
serial_number: u32,
|
|
|
|
/// The partitioned volume label. This can be set by the user else the default name will be
|
|
/// give: "NO NAME ".
|
|
volume_label: [11]u8,
|
|
|
|
/// The file system type, "FAT32 " for FAT32.
|
|
filesystem_type: [8]u8 = "FAT32 ".*,
|
|
};
|
|
|
|
/// Options used for initialising a new FAT image.
|
|
pub const Options = struct {
|
|
/// The FAT32 image size in bytes. This is not the formatted size.
|
|
/// The default (recommenced smallest) is 34090496 bytes (~32.5KB). This can be reduced to
|
|
/// the lowest value of 17920 bytes (17.5KB). This image size is the smallest possible size
|
|
/// where you can only create files, but not write to them. This image size can be mounted
|
|
/// in Linux and ZystemOS.
|
|
image_size: u32 = getDefaultImageSize(),
|
|
|
|
/// The sector size in bytes. The minimum value for this is 512, and a maximum of 4096.
|
|
/// This also must be a multiple of 512. Default 512.
|
|
sector_size: u16 = getDefaultSectorSize(),
|
|
|
|
/// The number of sectors per clusters. A default is chosen depending on the image size and
|
|
/// sector size. Valid value are: 1, 2, 4, 8, 16, 32, 64 and 128. Default 1.
|
|
cluster_size: u8 = getDefaultSectorsPerCluster(getDefaultImageSize(), getDefaultSectorSize()) catch unreachable,
|
|
|
|
/// The formatted volume name. Volume names shorter than 11 characters must have trailing
|
|
/// spaces. Default NO MANE.
|
|
volume_name: [11]u8 = getDefaultVolumeName(),
|
|
};
|
|
|
|
/// The error set for the static functions for creating a FAT32 image.
|
|
pub const Error = error{
|
|
/// If the FAT image that the user want's to create is too small: < 17.5KB.
|
|
TooSmall,
|
|
|
|
/// If the FAT image that the user want's to create is too large: > 2TB.
|
|
TooLarge,
|
|
|
|
/// The value in a option provided from the user is invalid.
|
|
InvalidOptionValue,
|
|
};
|
|
|
|
/// The log function for printing errors when creating a FAT32 image.
|
|
const log = std.log.scoped(.mkfat32);
|
|
|
|
/// The bootloader code for the FAT32 boot sector.
|
|
const bootsector_boot_code = [512]u8{
|
|
0xEB, 0x58, 0x90, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
|
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
|
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
|
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
|
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
|
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x66, 0xEA, 0x62, 0x7C, 0x00, 0x00,
|
|
0x00, 0x00, 0xFA, 0x8C, 0xC8, 0x8E, 0xD8, 0x8E, 0xC0, 0x8E, 0xD0, 0xBC, 0x00, 0x7C, 0x8D, 0x36,
|
|
0x8F, 0x7C, 0xB4, 0x0E, 0x31, 0xDB, 0xAC, 0x3C, 0x00, 0x74, 0x04, 0xCD, 0x10, 0xEB, 0xF7, 0xB0,
|
|
0x0A, 0xCD, 0x10, 0xB0, 0x0D, 0xCD, 0x10, 0x30, 0xE4, 0xCD, 0x16, 0xCD, 0x19, 0xEB, 0xFE, 0x54,
|
|
0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x6E, 0x6F, 0x74, 0x20, 0x61, 0x20, 0x62, 0x6F, 0x6F,
|
|
0x74, 0x61, 0x62, 0x6C, 0x65, 0x20, 0x64, 0x69, 0x73, 0x6B, 0x2E, 0x20, 0x50, 0x6C, 0x65, 0x61,
|
|
0x73, 0x65, 0x20, 0x69, 0x6E, 0x73, 0x65, 0x72, 0x74, 0x20, 0x61, 0x20, 0x62, 0x6F, 0x6F, 0x74,
|
|
0x61, 0x62, 0x6C, 0x65, 0x20, 0x66, 0x6C, 0x6F, 0x70, 0x70, 0x79, 0x20, 0x61, 0x6E, 0x64, 0x20,
|
|
0x70, 0x72, 0x65, 0x73, 0x73, 0x20, 0x61, 0x6E, 0x79, 0x20, 0x6B, 0x65, 0x79, 0x20, 0x74, 0x6F,
|
|
0x20, 0x74, 0x72, 0x79, 0x20, 0x61, 0x67, 0x61, 0x69, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xAA,
|
|
};
|
|
|
|
///
|
|
/// A convenient function for returning the error types for reading, writing and seeking a stream.
|
|
///
|
|
/// Argument:
|
|
/// IN comptime StreamType: type - The stream to get the error set from.
|
|
///
|
|
/// Return: type
|
|
/// The Error set for reading, writing and seeking the stream.
|
|
///
|
|
fn ErrorSet(comptime StreamType: type) type {
|
|
const WriteError = switch (@typeInfo(StreamType)) {
|
|
.Pointer => |p| p.child.WriteError,
|
|
else => StreamType.WriteError,
|
|
};
|
|
|
|
const SeekError = switch (@typeInfo(StreamType)) {
|
|
.Pointer => |p| p.child.SeekError,
|
|
else => StreamType.SeekError,
|
|
};
|
|
|
|
return WriteError || SeekError;
|
|
}
|
|
|
|
///
|
|
/// Get the number of reserved sectors. The number of reserved sectors doesn't have to be 32,
|
|
/// but this is a commonly used value.
|
|
///
|
|
/// Return: u16
|
|
/// The number of reserved sectors.
|
|
///
|
|
fn getReservedSectors() u16 {
|
|
return 32;
|
|
}
|
|
|
|
///
|
|
/// Get the default sector size for a FAT32 image. (512)
|
|
///
|
|
/// Return: u16
|
|
/// The default sector size (512).
|
|
///
|
|
fn getDefaultSectorSize() u16 {
|
|
// TODO: Look into if this could be different for different scenarios
|
|
return 512;
|
|
}
|
|
|
|
///
|
|
/// Based on the image size, the sector size per cluster can vary. See
|
|
/// https://support.microsoft.com/en-us/help/140365/default-cluster-size-for-ntfs-fat-and-exfat
|
|
/// but with some modifications to allow for smaller size images (The smallest image size possible is 17.5KB):
|
|
/// 17.5KB -> 64MB: 512 bytes
|
|
/// 64MB -> 128MB: 1024 bytes (1KB)
|
|
/// 128MB -> 256MB: 2048 bytes (2KB)
|
|
/// 256MB -> 8GB: 4096 bytes (4KB)
|
|
/// 8GB -> 16GB: 8192 bytes (8KB)
|
|
/// 16GB -> 32GB: 16384 bytes (16KB)
|
|
/// 32GB -> 2TB: 32768 bytes (32KB)
|
|
/// else: Error
|
|
///
|
|
/// The smallest size is calculated as follows:
|
|
/// (reserved_sectors + num_of_fats + root_directory) * smallest_sector_size = (32 + 2 + 1) * 512 = 35 * 512
|
|
///
|
|
/// Arguments:
|
|
/// IN image_size: u64 - The image size the user wants to create.
|
|
/// IN bytes_per_sector: u16 - The bytes per sector the user defined, or the default value.
|
|
///
|
|
/// Return: u16
|
|
/// The default sectors per cluster based on the image size and sector size.
|
|
///
|
|
/// Error: Error
|
|
/// error.TooSmall - If the image size is too small (below 35 * 512 (17.5KB))
|
|
/// error.TooLarge - If the image size is too large (above 2TB)
|
|
///
|
|
fn getDefaultSectorsPerCluster(image_size: u64, bytes_per_sector: u16) Error!u8 {
|
|
// A couple of constants to help
|
|
const KB = 1024;
|
|
const MB = 1024 * KB;
|
|
const GB = 1024 * MB;
|
|
const TB = 1024 * GB;
|
|
|
|
return switch (image_size) {
|
|
0...35 * 512 - 1 => Error.TooSmall,
|
|
35 * 512...64 * MB - 1 => @intCast(u8, std.math.max(512, bytes_per_sector) / bytes_per_sector),
|
|
64 * MB...128 * MB - 1 => @intCast(u8, std.math.max(1024, bytes_per_sector) / bytes_per_sector),
|
|
128 * MB...256 * MB - 1 => @intCast(u8, std.math.max(2048, bytes_per_sector) / bytes_per_sector),
|
|
256 * MB...8 * GB - 1 => @intCast(u8, std.math.max(4096, bytes_per_sector) / bytes_per_sector),
|
|
8 * GB...16 * GB - 1 => @intCast(u8, std.math.max(8192, bytes_per_sector) / bytes_per_sector),
|
|
16 * GB...32 * GB - 1 => @intCast(u8, std.math.max(16384, bytes_per_sector) / bytes_per_sector),
|
|
32 * GB...2 * TB - 1 => @intCast(u8, std.math.max(32768, bytes_per_sector) / bytes_per_sector),
|
|
else => Error.TooLarge,
|
|
};
|
|
}
|
|
|
|
///
|
|
/// Get the default volume name of a FAT image: "NO NAME ".
|
|
///
|
|
/// Return: [11]u8
|
|
/// The default volume name.
|
|
///
|
|
fn getDefaultVolumeName() [11]u8 {
|
|
return "NO NAME ".*;
|
|
}
|
|
|
|
///
|
|
/// Get the default image size: 34090496 (~32.5MB). This is the recommended minimum image size
|
|
/// for FAT32. (Valid cluster values + (sectors per FAT * 2) + reserved sectors) * bytes per sector.
|
|
///
|
|
/// Return: u32
|
|
/// The smallest recommended FAT32 image size.
|
|
///
|
|
fn getDefaultImageSize() u32 {
|
|
// The 513*2 was pre calculated
|
|
return (0xFFF5 + 1026 + @as(u32, getReservedSectors())) * @as(u32, getDefaultSectorSize());
|
|
}
|
|
|
|
///
|
|
/// Create the FAT32 serial number. This is generated from the date. Reference:
|
|
/// https://www.digital-detective.net/documents/Volume%20Serial%20Numbers.pdf
|
|
///
|
|
/// Return: u32
|
|
/// Serial number for a new FAT32 image.
|
|
///
|
|
fn createSerialNumber() u32 {
|
|
// TODO: Get the actual date. Currently there is no std lib for human readable date.
|
|
const year = 2020;
|
|
const month = 09;
|
|
const day = 27;
|
|
const hour = 13;
|
|
const minute = 46;
|
|
const second = 53;
|
|
const millisecond_10 = 54;
|
|
|
|
const p1 = (@as(u16, month) << 8) | day;
|
|
const p2 = (@as(u16, second) << 8) | millisecond_10;
|
|
const p3 = (@as(u16, hour) << 8) | minute;
|
|
|
|
const p4 = p1 + p2;
|
|
const p5 = p3 + year;
|
|
|
|
return (@as(u32, p4) << 16) + p5;
|
|
}
|
|
|
|
///
|
|
/// Write the FSInfo and backup FSInfo sector to a stream. This uses a valid FAT32 boot sector
|
|
/// header for creating the FSInfo sector.
|
|
///
|
|
/// Argument:
|
|
/// IN/OUT stream: anytype - The stream to write the FSInfo to.
|
|
/// IN fat32_header: Header - A valid FAT32 boot header for creating the FSInfo sector.
|
|
/// IN free_cluster_num: u32 - The number of free data clusters on the image.
|
|
/// IN next_free_cluster: u32 - The next free data cluster to start looking for when writing
|
|
/// files.
|
|
///
|
|
/// Error @TypeOf(stream).WriteError || @TypeOf(stream).SeekError
|
|
/// @TypeOf(stream).WriteError - If there is an error when writing. See the relevant error for the stream.
|
|
/// @TypeOf(stream).SeekError - If there is an error when seeking. See the relevant error for the stream.
|
|
///
|
|
fn writeFSInfo(stream: anytype, fat32_header: Header, free_cluster_num: u32, next_free_cluster: u32) ErrorSet(@TypeOf(stream))!void {
|
|
const seekable_stream = stream.seekableStream();
|
|
const writer = stream.writer();
|
|
|
|
// Seek to the correct location
|
|
try seekable_stream.seekTo(fat32_header.fsinfo_sector * fat32_header.bytes_per_sector);
|
|
|
|
// First signature
|
|
try writer.writeIntLittle(u32, 0x41615252);
|
|
|
|
// These next bytes are reserved and unused
|
|
try seekable_stream.seekBy(480);
|
|
|
|
// Second signature
|
|
try writer.writeIntLittle(u32, 0x61417272);
|
|
|
|
// Last know free cluster count
|
|
try writer.writeIntLittle(u32, free_cluster_num);
|
|
|
|
// Cluster number to start looking for available clusters
|
|
try writer.writeIntLittle(u32, next_free_cluster);
|
|
|
|
// These next bytes are reserved and unused
|
|
try seekable_stream.seekBy(12);
|
|
|
|
// Trail signature
|
|
try writer.writeIntLittle(u32, 0xAA550000);
|
|
|
|
// Repeat again for the backup
|
|
try seekable_stream.seekTo((fat32_header.backup_boot_sector + fat32_header.fsinfo_sector) * fat32_header.bytes_per_sector);
|
|
try writer.writeIntLittle(u32, 0x41615252);
|
|
try seekable_stream.seekBy(480);
|
|
try writer.writeIntLittle(u32, 0x61417272);
|
|
try writer.writeIntLittle(u32, free_cluster_num);
|
|
try writer.writeIntLittle(u32, next_free_cluster);
|
|
try seekable_stream.seekBy(12);
|
|
try writer.writeIntLittle(u32, 0xAA550000);
|
|
}
|
|
|
|
///
|
|
/// Write the FAT to the stream. This sets up a blank FAT with end marker of 0x0FFFFFFF and root
|
|
/// directory cluster of 0x0FFFFFFF (one cluster chain).
|
|
///
|
|
/// Argument:
|
|
/// IN/OUT stream: anytype - The stream to write the FSInfo to.
|
|
/// IN fat32_header: Header - A valid FAT32 boot header for creating the FAT.
|
|
///
|
|
/// Error @TypeOf(stream).WriteError || @TypeOf(stream).SeekError
|
|
/// @TypeOf(stream).WriteError - If there is an error when writing. See the relevant error for the stream.
|
|
/// @TypeOf(stream).SeekError - If there is an error when seeking. See the relevant error for the stream.
|
|
///
|
|
fn writeFAT(stream: anytype, fat32_header: Header) ErrorSet(@TypeOf(stream))!void {
|
|
const seekable_stream = stream.seekableStream();
|
|
const writer = stream.writer();
|
|
|
|
// This FAT is below the reserved sectors
|
|
try seekable_stream.seekTo(fat32_header.reserved_sectors * fat32_header.bytes_per_sector);
|
|
|
|
// Last byte is the same as the media descriptor, the rest are 1's.
|
|
try writer.writeIntLittle(u32, 0xFFFFFF00 | @as(u32, fat32_header.media_descriptor_type));
|
|
|
|
// End of chain indicator. This can be 0x0FFFFFF8 - 0x0FFFFFFF, but 0x0FFFFFFF is better supported.
|
|
try writer.writeIntLittle(u32, 0x0FFFFFFF);
|
|
|
|
// Root director cluster, 0x0FFFFFFF = initially only one cluster for root directory
|
|
try writer.writeIntLittle(u32, 0x0FFFFFFF);
|
|
|
|
// Write the second FAT, same as the first
|
|
try seekable_stream.seekTo(fat32_header.reserved_sectors * fat32_header.bytes_per_sector + (fat32_header.sectors_per_fat * fat32_header.bytes_per_sector));
|
|
try writer.writeIntLittle(u32, 0xFFFFFF00 | @as(u32, fat32_header.media_descriptor_type));
|
|
try writer.writeIntLittle(u32, 0x0FFFFFFF);
|
|
try writer.writeIntLittle(u32, 0x0FFFFFFF);
|
|
}
|
|
|
|
///
|
|
/// Write the FAT boot sector with the boot code and FAT32 header to the stream.
|
|
///
|
|
/// Argument:
|
|
/// IN/OUT stream: anytype - The stream to write the FSInfo to.
|
|
/// IN fat32_header: Header - A valid FAT32 boot header for creating the FAT.
|
|
///
|
|
/// Error: @TypeOf(stream).WriteError || @TypeOf(stream).SeekError
|
|
/// @TypeOf(stream).WriteError - If there is an error when writing. See the relevant error for the stream.
|
|
/// @TypeOf(stream).SeekError - If there is an error when seeking. See the relevant error for the stream.
|
|
///
|
|
fn writeBootSector(stream: anytype, fat32_header: Header) ErrorSet(@TypeOf(stream))!void {
|
|
const seekable_stream = stream.seekableStream();
|
|
const writer = stream.writer();
|
|
|
|
var boot_sector: [512]u8 = undefined;
|
|
std.mem.copy(u8, &boot_sector, &bootsector_boot_code);
|
|
|
|
// Write the header into the boot sector variable
|
|
var fat32_header_stream = std.io.fixedBufferStream(boot_sector[3..90]);
|
|
inline for (std.meta.fields(Header)) |item| {
|
|
switch (@typeInfo(item.field_type)) {
|
|
.Array => |info| switch (info.child) {
|
|
u8 => try fat32_header_stream.writer().writeAll(&@field(fat32_header, item.name)),
|
|
else => @compileError("Unexpected field type: " ++ @typeName(info.child)),
|
|
},
|
|
.Int => try fat32_header_stream.writer().writeIntLittle(item.field_type, @field(fat32_header, item.name)),
|
|
else => @compileError("Unexpected field type: " ++ @typeName(info.child)),
|
|
}
|
|
}
|
|
|
|
// Write the bootstrap and header sector to the image
|
|
try seekable_stream.seekTo(0);
|
|
try writer.writeAll(&boot_sector);
|
|
|
|
// Also write the backup sector to the image
|
|
try seekable_stream.seekTo(fat32_header.backup_boot_sector * fat32_header.bytes_per_sector);
|
|
try writer.writeAll(&boot_sector);
|
|
}
|
|
|
|
///
|
|
/// Create a valid FAT32 header with out the bootstrap code with either user defined parameters
|
|
/// or default FAT32 parameters.
|
|
///
|
|
/// Argument:
|
|
/// IN options: Options - The values provided by the user or default values.
|
|
///
|
|
/// Return: Fat32.Header
|
|
/// A valid FAT32 header without the bootstrap code.
|
|
///
|
|
/// Error: Error
|
|
/// Error.InvalidOptionValue - If the values provided by the user are invalid and/or out of
|
|
/// bounds. A log message will be printed to display the valid
|
|
/// ranges.
|
|
///
|
|
fn createFATHeader(options: Options) Error!Header {
|
|
// 512 is the smallest sector size for FAT32
|
|
if (options.sector_size < 512 or options.sector_size > 4096 or options.sector_size % 512 != 0) {
|
|
log.err("Bytes per sector is invalid. Must be greater then 512 and a multiple of 512. Found: {}", .{options.sector_size});
|
|
return Error.InvalidOptionValue;
|
|
}
|
|
|
|
// Valid clusters are 1, 2, 4, 8, 16, 32, 64 and 128
|
|
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;
|
|
}
|
|
|
|
// Ensure the image is aligned to the bytes per sector
|
|
// Backwards as if being imaged to a device, we can't can't go over
|
|
const image_size = std.mem.alignBackward(options.image_size, options.sector_size);
|
|
|
|
// This is the bare minimum. It wont hold any data in a file, but can create some files. But it still can be mounted in Linux
|
|
// +1 for the root director sector, 2 TB
|
|
// FAT count for FAT32 is always 2
|
|
if (image_size < ((getReservedSectors() + 2 + 1) * options.sector_size) or image_size >= 2 * 1024 * 1024 * 1024 * 1024) {
|
|
log.err("Image size is invalid. Must be greater then 17919 (~18KB). Found: {}", .{image_size});
|
|
return Error.InvalidOptionValue;
|
|
}
|
|
|
|
// See: https://board.flatassembler.net/topic.php?t=12680
|
|
var sectors_per_fat = @intCast(u32, (image_size - getReservedSectors() + (2 * options.cluster_size)) / ((options.cluster_size * (options.sector_size / 4)) + 2));
|
|
// round up sectors
|
|
sectors_per_fat = (sectors_per_fat + options.sector_size - 1) / options.sector_size;
|
|
|
|
return Header{
|
|
.bytes_per_sector = options.sector_size,
|
|
.sectors_per_cluster = options.cluster_size,
|
|
.total_sectors = @intCast(u32, @divExact(image_size, options.sector_size)),
|
|
.sectors_per_fat = sectors_per_fat,
|
|
.serial_number = createSerialNumber(),
|
|
.volume_label = options.volume_name,
|
|
};
|
|
}
|
|
|
|
///
|
|
/// Make a FAT32 image. This will either use the default options or modified defaults from the
|
|
/// user. The file will be saved to the path specified. If quick format is on, then the entire
|
|
/// stream is zeroed else the reserved and FAT sectors are zeroed.
|
|
///
|
|
/// Argument:
|
|
/// IN options: Options - The FAT32 options that the user can provide to change the
|
|
/// parameters of a FAT32 image.
|
|
/// IN stream: anytype - The stream to create a new FAT32 image. This stream must
|
|
/// support reader(), writer() and seekableStream() interfaces.
|
|
/// IN quick_format: bool - Whether to completely zero the stream initially or zero just
|
|
/// the important sectors.
|
|
///
|
|
/// Error: @TypeOf(stream).WriteError || @TypeOf(stream).SeekError || Error
|
|
/// @TypeOf(stream).WriteError - If there is an error when writing. See the relevant error for the stream.
|
|
/// @TypeOf(stream).SeekError - If there is an error when seeking. See the relevant error for the stream.
|
|
/// Error.InvalidOptionValue - In the user has provided invalid options.
|
|
/// Error.TooLarge - The stream size is too small. < 17.5KB.
|
|
/// Error.TooSmall - The stream size is to large. > 2TB.
|
|
///
|
|
pub fn make(options: Options, stream: anytype, quick_format: bool) (ErrorSet(@TypeOf(stream)) || Error)!void {
|
|
// First set up the header
|
|
const fat32_header = try Fat32.createFATHeader(options);
|
|
|
|
// Initialise the stream with all zeros
|
|
try stream.seekableStream().seekTo(0);
|
|
if (quick_format) {
|
|
// Zero just the reserved and FAT sectors
|
|
try stream.writer().writeByteNTimes(0x00, (fat32_header.reserved_sectors + (fat32_header.sectors_per_fat * 2)) * fat32_header.bytes_per_sector);
|
|
} else {
|
|
const image_size = std.mem.alignBackward(options.image_size, fat32_header.bytes_per_sector);
|
|
try stream.writer().writeByteNTimes(0x00, image_size);
|
|
}
|
|
|
|
// Write the boot sector with the bootstrap code and header and the backup boot sector.
|
|
try Fat32.writeBootSector(stream, fat32_header);
|
|
|
|
// Write the FAT and second FAT
|
|
try Fat32.writeFAT(stream, fat32_header);
|
|
|
|
// Calculate the usable clusters.
|
|
const usable_sectors = fat32_header.total_sectors - fat32_header.reserved_sectors - (fat32_header.fat_count * fat32_header.sectors_per_fat);
|
|
const usable_clusters = @divFloor(usable_sectors, fat32_header.sectors_per_cluster) - 1;
|
|
|
|
// Write the FSInfo and backup FSInfo sectors
|
|
try Fat32.writeFSInfo(stream, fat32_header, usable_clusters, 2);
|
|
}
|
|
};
|