788bef5029
Move to `log` Fixed kmain log
574 lines
27 KiB
Zig
574 lines
27 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,
|
|
};
|
|
|
|
///
|
|
/// 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.5KB). 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) (@TypeOf(stream).WriteError || @TypeOf(stream).SeekError)!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) (@TypeOf(stream).WriteError || @TypeOf(stream).SeekError)!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) (@TypeOf(stream).WriteError || @TypeOf(stream).SeekError)!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 < 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});
|
|
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.
|
|
///
|
|
/// 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.
|
|
///
|
|
/// 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) (@TypeOf(stream).WriteError || @TypeOf(stream).SeekError || Error)!void {
|
|
// First set up the header
|
|
const fat32_header = try Fat32.createFATHeader(options);
|
|
// Get the total image size again. As the above has a check for the size, we don't need one here again
|
|
const image_size = std.mem.alignBackward(options.image_size, fat32_header.bytes_per_sector);
|
|
|
|
// Initialise the stream with all zeros
|
|
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);
|
|
}
|
|
};
|