Create a blank FAT32 image
This will be used for testing the FAT32 driver for the kernel and will be integrated into the OS as a mkfs.fat32 program. Plus typos Fixed dependencies Removed `fat32_` in options Plus fixed doc comment Removed the DefaultOrValue Also reordered some stuff Removed the serial time for more parameters Moved writer() and seekableStream() to variables Refactored mkFAT32
This commit is contained in:
parent
403fb1668f
commit
c9a9be8182
3 changed files with 645 additions and 7 deletions
70
build.zig
70
build.zig
|
@ -1,4 +1,5 @@
|
|||
const std = @import("std");
|
||||
const logger = std.log.scoped(.builder);
|
||||
const builtin = @import("builtin");
|
||||
const rt = @import("test/runtime_test.zig");
|
||||
const RuntimeStep = rt.RuntimeStep;
|
||||
|
@ -12,6 +13,7 @@ const File = fs.File;
|
|||
const Mode = builtin.Mode;
|
||||
const TestMode = rt.TestMode;
|
||||
const ArrayList = std.ArrayList;
|
||||
const Fat32 = @import("mkfat32.zig").Fat32;
|
||||
|
||||
const x86_i686 = CrossTarget{
|
||||
.cpu_arch = .i386,
|
||||
|
@ -28,6 +30,7 @@ pub fn build(b: *Builder) !void {
|
|||
|
||||
const fmt_step = b.addFmt(&[_][]const u8{
|
||||
"build.zig",
|
||||
"mkfat32.zig",
|
||||
"src",
|
||||
"test",
|
||||
});
|
||||
|
@ -42,6 +45,7 @@ pub fn build(b: *Builder) !void {
|
|||
const boot_path = try fs.path.join(b.allocator, &[_][]const u8{ b.exe_dir, "iso", "boot" });
|
||||
const modules_path = try fs.path.join(b.allocator, &[_][]const u8{ b.exe_dir, "iso", "modules" });
|
||||
const ramdisk_path = try fs.path.join(b.allocator, &[_][]const u8{ b.install_path, "initrd.ramdisk" });
|
||||
const fat32_image_path = try fs.path.join(b.allocator, &[_][]const u8{ b.install_path, "fat32.img" });
|
||||
|
||||
const build_mode = b.standardReleaseOptions();
|
||||
comptime var test_mode_desc: []const u8 = "\n ";
|
||||
|
@ -68,6 +72,9 @@ pub fn build(b: *Builder) !void {
|
|||
};
|
||||
make_iso.step.dependOn(&exec.step);
|
||||
|
||||
var fat32_builder_step = Fat32BuilderStep.create(b, .{}, fat32_image_path);
|
||||
make_iso.step.dependOn(&fat32_builder_step.step);
|
||||
|
||||
var ramdisk_files_al = ArrayList([]const u8).init(b.allocator);
|
||||
defer ramdisk_files_al.deinit();
|
||||
|
||||
|
@ -166,6 +173,58 @@ pub fn build(b: *Builder) !void {
|
|||
debug_step.dependOn(&debug_cmd.step);
|
||||
}
|
||||
|
||||
/// The FAT32 step for creating a FAT32 image.
|
||||
const Fat32BuilderStep = struct {
|
||||
/// The Step, that is all you need to know
|
||||
step: Step,
|
||||
|
||||
/// The builder pointer, also all you need to know
|
||||
builder: *Builder,
|
||||
|
||||
/// The path to where the ramdisk will be written to.
|
||||
out_file_path: []const u8,
|
||||
|
||||
/// Options for creating the FAT32 image.
|
||||
options: Fat32.Options,
|
||||
|
||||
///
|
||||
/// The make function that is called by the builder.
|
||||
///
|
||||
/// Arguments:
|
||||
/// IN step: *Step - The step of this step.
|
||||
///
|
||||
/// Error: error{EndOfStream} || File.OpenError || File.ReadError || File.WriteError || File.SeekError || Allocator.Error || Fat32.Error || Error
|
||||
/// error{EndOfStream} || File.OpenError || File.ReadError || File.WriteError || File.SeekError - Error related to file operations. See std.fs.File.
|
||||
/// Allocator.Error - If there isn't enough memory to allocate for the make step.
|
||||
/// Fat32.Error - If there was an error creating the FAT image. This will be invalid options.
|
||||
///
|
||||
fn make(step: *Step) (error{EndOfStream} || File.OpenError || File.ReadError || File.WriteError || File.SeekError || Fat32.Error)!void {
|
||||
const self = @fieldParentPtr(Fat32BuilderStep, "step", step);
|
||||
try Fat32.make(self.options, self.out_file_path);
|
||||
}
|
||||
|
||||
///
|
||||
/// Create a FAT32 builder step.
|
||||
///
|
||||
/// Argument:
|
||||
/// IN builder: *Builder - The build builder.
|
||||
/// IN options: Options - Options for creating FAT32 image.
|
||||
///
|
||||
/// Return: *Fat32BuilderStep
|
||||
/// The FAT32 builder step pointer to add to the build process.
|
||||
///
|
||||
pub fn create(builder: *Builder, options: Fat32.Options, out_file_path: []const u8) *Fat32BuilderStep {
|
||||
const fat32_builder_step = builder.allocator.create(Fat32BuilderStep) catch unreachable;
|
||||
fat32_builder_step.* = .{
|
||||
.step = Step.init(.Custom, builder.fmt("Fat32BuilderStep", .{}), builder.allocator, make),
|
||||
.builder = builder,
|
||||
.options = options,
|
||||
.out_file_path = out_file_path,
|
||||
};
|
||||
return fat32_builder_step;
|
||||
}
|
||||
};
|
||||
|
||||
/// The ramdisk make step for creating the initial ramdisk.
|
||||
const RamdiskStep = struct {
|
||||
/// The Step, that is all you need to know
|
||||
|
@ -198,7 +257,7 @@ const RamdiskStep = struct {
|
|||
/// Errors for opening, reading and writing to and from files and for allocating memory.
|
||||
///
|
||||
fn writeRamdisk(comptime Usize: type, self: *RamdiskStep) Error!void {
|
||||
// 1MB, don't think the ram disk should be very big
|
||||
// 1GB, don't think the ram disk should be very big
|
||||
const max_file_size = 1024 * 1024 * 1024;
|
||||
|
||||
// Open the out file
|
||||
|
@ -240,9 +299,8 @@ const RamdiskStep = struct {
|
|||
}
|
||||
|
||||
///
|
||||
/// The make function that is called by the builder. This will create the qemu process with the
|
||||
/// stdout as a Pipe. Then create the read thread to read the logs from the qemu stdout. Then
|
||||
/// will call the test function to test a specifics part of the OS defined by the test mode.
|
||||
/// The make function that is called by the builder. This will switch on the target to get the
|
||||
/// correct usize length for the target.
|
||||
///
|
||||
/// Arguments:
|
||||
/// IN step: *Step - The step of this step.
|
||||
|
@ -262,8 +320,8 @@ const RamdiskStep = struct {
|
|||
/// Create a ramdisk step.
|
||||
///
|
||||
/// Argument:
|
||||
/// IN builder: *Builder - The build builder.
|
||||
/// IN target: CrossTarget - The target for the build.
|
||||
/// IN builder: *Builder - The build builder.
|
||||
/// IN target: CrossTarget - The target for the build.
|
||||
/// IN files: []const []const u8 - The file names to be added to the ramdisk.
|
||||
/// IN out_file_path: []const u8 - The output file path.
|
||||
///
|
||||
|
|
580
mkfat32.zig
Normal file
580
mkfat32.zig
Normal file
|
@ -0,0 +1,580 @@
|
|||
const std = @import("std");
|
||||
const File = std.fs.File;
|
||||
|
||||
// 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 logger function for printing errors when creating a FAT32 image.
|
||||
const logger = 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 image file. This uses a valid FAT32 boot
|
||||
/// sector header for creating the FSInfo sector.
|
||||
///
|
||||
/// Argument:
|
||||
/// IN/OUT image: File - The image file 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 File.WriteError || File.SeekError
|
||||
/// File.WriteError - If there is an error when writing to the image. See File.WriteError
|
||||
/// File.SeekError - If there is an error when seeking the image. See File.SeekError
|
||||
///
|
||||
fn writeFSInfo(image: File, fat32_header: Header, free_cluster_num: u32, next_free_cluster: u32) (File.WriteError || File.SeekError)!void {
|
||||
const seekable_stream = image.seekableStream();
|
||||
const writer = image.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 image. This sets up a blank FAT with end marker of 0x0FFFFFFF and root
|
||||
/// directory cluster of 0x0FFFFFFF (one cluster chain).
|
||||
///
|
||||
/// Argument:
|
||||
/// IN/OUT image: File - The image file to write the FSInfo to.
|
||||
/// IN fat32_header: Header - A valid FAT32 boot header for creating the FAT.
|
||||
///
|
||||
/// Error File.WriteError || File.SeekError
|
||||
/// File.WriteError - If there is an error when writing to the image. See File.WriteError
|
||||
/// File.SeekError - If there is an error when seeking the image. See File.SeekError
|
||||
///
|
||||
fn writeFAT(image: File, fat32_header: Header) (File.WriteError || File.SeekError)!void {
|
||||
const seekable_stream = image.seekableStream();
|
||||
const writer = image.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 image.
|
||||
///
|
||||
/// Argument:
|
||||
/// IN/OUT image: File - The image file to write the FSInfo to.
|
||||
/// IN fat32_header: Header - A valid FAT32 boot header for creating the FAT.
|
||||
///
|
||||
/// Error: File.WriteError || File.SeekError
|
||||
/// File.WriteError - If there is an error when writing to the image. See File.WriteError.
|
||||
/// File.SeekError - If there is an error when seeking the image. See File.SeekError.
|
||||
///
|
||||
fn writeBootSector(image: File, fat32_header: Header) (File.WriteError || File.SeekError)!void {
|
||||
const seekable_stream = image.seekableStream();
|
||||
const writer = image.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) {
|
||||
logger.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)) {
|
||||
logger.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) {
|
||||
logger.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 out_file_path: []const u8 - The location for which the FAT32 image will be created.
|
||||
///
|
||||
/// Error: File.OpenError || File.WriteError || File.SeekError || Error
|
||||
/// File.OpenError || File.WriteError || File.SeekError - Error relating to file operations. See std.fs.File.
|
||||
/// Error.InvalidOptionValue - In the user has provided invalid options.
|
||||
/// Error.TooLarge - The image size is too small. < 17.5KB.
|
||||
/// Error.TooSmall - The image size is to large. > 2TB.
|
||||
///
|
||||
pub fn make(options: Options, out_file_path: []const u8) (File.OpenError || File.WriteError || File.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 image size, we don't need one here again
|
||||
const image_size = std.mem.alignBackward(options.image_size, fat32_header.bytes_per_sector);
|
||||
|
||||
// Open the out file
|
||||
const image = try std.fs.cwd().createFile(out_file_path, .{ .read = true });
|
||||
|
||||
// If there was an error, delete the image as this will be invalid
|
||||
errdefer (std.fs.cwd().deleteFile(out_file_path) catch unreachable);
|
||||
defer image.close();
|
||||
|
||||
// Initialise the image with all zeros
|
||||
try image.writer().writeByteNTimes(0x00, image_size);
|
||||
|
||||
// Write the boot sector with the bootstrap code and header and the backup boot sector.
|
||||
try Fat32.writeBootSector(image, fat32_header);
|
||||
|
||||
// Write the FAT and second FAT
|
||||
try Fat32.writeFAT(image, 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(image, fat32_header, usable_clusters, 2);
|
||||
}
|
||||
};
|
|
@ -179,7 +179,7 @@ pub const RuntimeStep = struct {
|
|||
}
|
||||
|
||||
///
|
||||
/// The make function that is called by the builder. This will create the qemu process with the
|
||||
/// The make function that is called by the builder. This will create a qemu process with the
|
||||
/// stdout as a Pipe. Then create the read thread to read the logs from the qemu stdout. Then
|
||||
/// will call the test function to test a specifics part of the OS defined by the test mode.
|
||||
///
|
||||
|
|
Loading…
Reference in a new issue