From b3ac0aa0d4a333f7151e72c40f8f7dcdf684a50b Mon Sep 17 00:00:00 2001 From: Edward Dean Date: Fri, 7 May 2021 20:28:58 +0100 Subject: [PATCH] Added MBR implementation This adds the a MBR partition implementation. Refactored some file system code to have a common FS helper functions This also enables the using the MBR and FAT32 to create the boot image removing the echfs dependency. Moved file system boot sector to common x86 arch. As this boot sector is written in x86, where would need to be an ARM version, so would be arch dependent. Added MAP file path Fixed running the x86_64 port as this wasn't making the boot image, just the elf --- .gitmodules | 3 - build.zig | 315 +++++++++++--- echfs | 1 - makeiso_64.sh | 42 +- src/kernel/arch/x86/64bit/boot.zig | 2 +- src/kernel/arch/x86/common/arch.zig | 91 +++- src/kernel/filesystem/fat32.zig | 215 ++++----- src/kernel/filesystem/filesystem_common.zig | 90 ++++ .../kernel/filesystem/makefs.zig | 341 ++++++++------- src/kernel/filesystem/mbr.zig | 408 ++++++++++++++++++ src/kernel/kmain_64.zig | 4 + src/kernel/time.zig | 31 ++ test/mock/kernel/arch_mock.zig | 35 ++ 13 files changed, 1201 insertions(+), 377 deletions(-) delete mode 160000 echfs create mode 100644 src/kernel/filesystem/filesystem_common.zig rename mkfat32.zig => src/kernel/filesystem/makefs.zig (71%) create mode 100644 src/kernel/filesystem/mbr.zig create mode 100644 src/kernel/time.zig diff --git a/.gitmodules b/.gitmodules index b66d6c8e..a28652eb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ [submodule "limine"] path = limine url = https://github.com/limine-bootloader/limine.git -[submodule "echfs"] - path = echfs - url = https://github.com/echfs/echfs.git diff --git a/build.zig b/build.zig index 40fd8f6e..634c2506 100644 --- a/build.zig +++ b/build.zig @@ -13,7 +13,12 @@ const File = fs.File; const Mode = builtin.Mode; const TestMode = rt.TestMode; const ArrayList = std.ArrayList; -const Fat32 = @import("mkfat32.zig").Fat32; +const makefs = @import("src/kernel/filesystem/makefs.zig"); + +const fat32_driver = @import("src/kernel/filesystem/fat32.zig"); +const mbr_driver = @import("src/kernel/filesystem/mbr.zig"); + +const FromTo = struct { from: []const u8, to: []const u8 }; const x86_i686 = CrossTarget{ .cpu_arch = .i386, @@ -54,7 +59,6 @@ pub fn build(b: *Builder) !void { const fmt_step = b.addFmt(&[_][]const u8{ "build.zig", - "mkfat32.zig", "src", "test", }); @@ -72,8 +76,9 @@ 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 test_fat32_image_path = try fs.path.join(b.allocator, &[_][]const u8{ "test", "fat32", "test_fat32.img" }); + const boot_drive_image_path = try fs.path.join(b.allocator, &[_][]const u8{ b.install_path, "boot_drive.img" }); + const kernel_map_path = try fs.path.join(b.allocator, &[_][]const u8{ b.install_path, "kernel.map" }); const build_mode = b.standardReleaseOptions(); comptime var test_mode_desc: []const u8 = "\n "; @@ -96,14 +101,12 @@ pub fn build(b: *Builder) !void { const make_iso = switch (target.getCpuArch()) { .i386 => b.addSystemCommand(&[_][]const u8{ "./makeiso.sh", boot_path, modules_path, iso_dir_path, exec.getOutputPath(), ramdisk_path, output_iso }), - .x86_64 => b.addSystemCommand(&[_][]const u8{ "./makeiso_64.sh", b.install_path, b.exe_dir, exec.getOutputPath(), output_iso, ramdisk_path }), + .x86_64 => b.addSystemCommand(&[_][]const u8{ "./makeiso_64.sh", kernel_map_path, exec.getOutputPath(), output_iso, ramdisk_path }), else => unreachable, }; make_iso.step.dependOn(&exec.step); - var fat32_builder_step = Fat32BuilderStep.create(b, .{}, fat32_image_path); - make_iso.step.dependOn(&fat32_builder_step.step); - + // Make the init ram disk var ramdisk_files_al = ArrayList([]const u8).init(b.allocator); defer ramdisk_files_al.deinit(); @@ -131,7 +134,47 @@ pub fn build(b: *Builder) !void { const ramdisk_step = RamdiskStep.create(b, target, ramdisk_files_al.toOwnedSlice(), ramdisk_path); make_iso.step.dependOn(&ramdisk_step.step); - b.default_step.dependOn(&make_iso.step); + var make_bootable = make_iso; + + // Making the boot image is for the 64 bit port + switch (target.getCpuArch()) { + .i386 => b.default_step.dependOn(&make_iso.step), + .x86_64 => { + const boot_drive_image = try b.allocator.create(std.fs.File); + errdefer b.allocator.destroy(boot_drive_image); + + try std.fs.cwd().makePath(b.install_path); + boot_drive_image.* = try std.fs.cwd().createFile(boot_drive_image_path, .{ .read = true }); + + // If there was an error, delete the image as this will be invalid + errdefer (std.fs.cwd().deleteFile(boot_drive_image_path) catch unreachable); + + var files_path = ArrayList(FromTo).init(b.allocator); + defer files_path.deinit(); + try files_path.append(.{ .from = "./limine.cfg", .to = "limine.cfg" }); + try files_path.append(.{ .from = "./limine/limine.sys", .to = "limine.sys" }); + try files_path.append(.{ .from = exec.getOutputPath(), .to = "pluto.elf" }); + try files_path.append(.{ .from = ramdisk_path, .to = "initrd.ramdisk" }); + try files_path.append(.{ .from = kernel_map_path, .to = "kernel.map" }); + + const mbr_builder_options = BootDriveStep(@TypeOf(boot_drive_image)).Options{ + .mbr_options = .{ + .partition_options = .{ 100, null, null, null }, + .image_size = (makefs.Fat32.Options{}).image_size + makefs.MBRPartition.getReservedStartSize(), + }, + .fs_type = BootDriveStep(@TypeOf(boot_drive_image)).FSType{ .FAT32 = .{} }, + }; + const make_boot_drive_step = BootDriveStep(@TypeOf(boot_drive_image)).create(b, mbr_builder_options, boot_drive_image, files_path.toOwnedSlice()); + + make_bootable = b.addSystemCommand(&[_][]const u8{ "./limine/limine-install", boot_drive_image_path }); + + make_boot_drive_step.step.dependOn(&make_iso.step); + make_boot_drive_step.step.dependOn(&ramdisk_step.step); + make_bootable.step.dependOn(&make_boot_drive_step.step); + b.default_step.dependOn(&make_bootable.step); + }, + else => unreachable, + } const test_step = b.step("test", "Run tests"); const mock_path = "../../test/mock/kernel/"; @@ -154,8 +197,19 @@ pub fn build(b: *Builder) !void { const mock_gen_run = mock_gen.run(); unit_tests.step.dependOn(&mock_gen_run.step); + const test_fat32_image = try b.allocator.create(std.fs.File); + errdefer b.allocator.destroy(test_fat32_image); + + // Open the out file + test_fat32_image.* = try std.fs.cwd().createFile(test_fat32_image_path, .{ .read = true }); + + // If there was an error, delete the image as this will be invalid + errdefer (std.fs.cwd().deleteFile(test_fat32_image_path) catch unreachable); + + const test_files_path = &[_]FromTo{}; + // Create test FAT32 image - const test_fat32_img_step = Fat32BuilderStep.create(b, .{}, test_fat32_image_path); + const test_fat32_img_step = Fat32BuilderStep(@TypeOf(test_fat32_image)).create(b, .{}, test_fat32_image, test_files_path); const copy_test_files_step = b.addSystemCommand(&[_][]const u8{ "./fat32_cp.sh", test_fat32_image_path }); copy_test_files_step.step.dependOn(&test_fat32_img_step.step); unit_tests.step.dependOn(©_test_files_step.step); @@ -182,7 +236,7 @@ pub fn build(b: *Builder) !void { }, .x86_64 => { try qemu_args_al.append("-drive"); - try qemu_args_al.append(try std.mem.join(b.allocator, "", &[_][]const u8{ "format=raw,file=", output_iso })); + try qemu_args_al.append(try std.mem.join(b.allocator, "", &[_][]const u8{ "format=raw,file=", boot_drive_image_path })); }, else => unreachable, } @@ -196,7 +250,7 @@ pub fn build(b: *Builder) !void { // 64 bit build don't have full support for these tests yet if (target.getCpuArch() == .i386) { const rt_step = RuntimeStep.create(b, test_mode, qemu_args); - rt_step.step.dependOn(&make_iso.step); + rt_step.step.dependOn(&make_bootable.step); rt_test_step.dependOn(&rt_step.step); } @@ -207,8 +261,8 @@ pub fn build(b: *Builder) !void { const qemu_debug_cmd = b.addSystemCommand(qemu_args); qemu_debug_cmd.addArgs(&[_][]const u8{ "-s", "-S" }); - qemu_cmd.step.dependOn(&make_iso.step); - qemu_debug_cmd.step.dependOn(&make_iso.step); + qemu_cmd.step.dependOn(&make_bootable.step); + qemu_debug_cmd.step.dependOn(&make_bootable.step); run_step.dependOn(&qemu_cmd.step); run_debug_step.dependOn(&qemu_debug_cmd.step); @@ -229,63 +283,196 @@ 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 step to create a bootable drive. +fn BootDriveStep(comptime StreamType: type) type { + return struct { + /// The Step, that is all you need to know + step: Step, - /// The builder pointer, also all you need to know - builder: *Builder, + /// 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, + /// The stream to write the boot drive to. + stream: StreamType, - /// Options for creating the FAT32 image. - options: Fat32.Options, + /// Options for creating the MBR partition scheme. + options: 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); - // Open the out file - const image = try std.fs.cwd().createFile(self.out_file_path, .{ .read = true }); + /// The list of file paths to copy into the partition created image. + files: []const FromTo, - // If there was an error, delete the image as this will be invalid - errdefer (std.fs.cwd().deleteFile(self.out_file_path) catch unreachable); - defer image.close(); - try Fat32.make(self.options, image, false); - } + const Self = @This(); - /// - /// 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, + /// The union of filesystems that the partition scheme will use to format the partition + /// with. TODO: Support multiple partitions with different filesystem types. + const FSType = union(enum) { + /// The FAT32 filesystem with the make FAT32 options. + FAT32: makefs.Fat32.Options, }; - return fat32_builder_step; - } -}; + + /// The options for creating the boot drive. + const Options = struct { + /// The MBR options for creating the boot drive. + mbr_options: makefs.MBRPartition.Options, + + /// The filesystem type to format the boot drive with. + fs_type: FSType, + }; + + /// + /// The make function that is called by the builder. + /// + /// Arguments: + /// IN step: *Step - The step of this step. + /// + /// Error: anyerror + /// There are too many error to type out but errors will relate to allocation errors + /// and file open, read, write and seek. + /// + fn make(step: *Step) anyerror!void { + const self = @fieldParentPtr(Self, "step", step); + // TODO: Here check the options for what FS for what partition + try makefs.MBRPartition.make(self.options.mbr_options, self.stream); + const mbr_fs = &(try mbr_driver.MBRPartition(StreamType).init(self.builder.allocator, self.stream)); + switch (self.options.fs_type) { + .FAT32 => |*options| { + const partition_stream = &(try mbr_fs.getPartitionStream(0)); + // Overwrite the image size to what we have + options.image_size = @intCast(u32, try partition_stream.seekableStream().getEndPos()); + try Fat32BuilderStep(*mbr_driver.PartitionStream(StreamType)).make2(self.builder.allocator, options.*, partition_stream, self.files); + }, + } + } + + /// + /// Create a boot drive step. + /// + /// Arguments: + /// IN builder: *Builder - The builder. + /// IN options: Options - The options used to configure the boot drive. + /// IN stream: StreamType - The stream to write the boot drive to. + /// IN files: []const FromTo - The files to copy to the boot drive using the filesystem type. + /// + /// Return: *Self + /// Pointer to the boot drive step. + /// + pub fn create(builder: *Builder, options: Options, stream: StreamType, files: []const FromTo) *Self { + const boot_driver_step = builder.allocator.create(Self) catch unreachable; + boot_driver_step.* = .{ + .step = Step.init(.Custom, builder.fmt("BootDriveStep", .{}), builder.allocator, make), + .builder = builder, + .stream = stream, + .options = options, + .files = files, + }; + return boot_driver_step; + } + }; +} + +/// +/// The FAT32 step for creating a FAT32 image. This now takes a stream type. +/// +/// Arguments: +/// IN comptime StreamType: type - The stream type the FAT32 builder step will use. +/// +/// Return: type +/// The types FAT32 builder step. +/// +fn Fat32BuilderStep(comptime StreamType: type) type { + return struct { + /// The Step, that is all you need to know + step: Step, + + /// The builder pointer, also all you need to know + builder: *Builder, + + /// The stream to write the FAT32 headers and files to. + stream: StreamType, + + /// Options for creating the FAT32 image. + options: makefs.Fat32.Options, + + /// The list of file paths to copy into the FAT32 image. + files: []const FromTo, + + const Self = @This(); + + /// + /// The make function that is called by the builder. + /// + /// Arguments: + /// IN step: *Step - The step of this step. + /// + /// Error: anyerror + /// There are too many error to type out but errors will relate to allocation errors + /// and file open, read, write and seek. + /// + fn make(step: *Step) anyerror!void { + const self = @fieldParentPtr(Self, "step", step); + try make2(self.builder.allocator, self.options, self.stream, self.files); + } + + /// + /// A standard method for making a FAT32 filesystem on a stream already partitioned. + /// + /// Arguments: + /// IN allocator: *Allocator - An allocator for memory allocation. + /// IN options: makefs.Fat32.Options - The options to make a FAT32 filesystem. + /// IN stream: StreamType - The stream to write the filesystem to. This will + /// be from a getPartitionStream(). + /// IN files: []const FromTo - The file to write to the file system. + /// + /// Error: anyerror + /// There are too many error to type out but errors will relate to allocation errors + /// and file open, read, write and seek. + /// + pub fn make2(allocator: *Allocator, options: makefs.Fat32.Options, stream: StreamType, files: []const FromTo) anyerror!void { + try makefs.Fat32.make(options, stream); + + // Copy the files into the image + var fat32_image = try fat32_driver.initialiseFAT32(allocator, stream); + defer fat32_image.destroy() catch unreachable; + for (files) |file_path| { + const opened_node = try fat32_image.fs.open(fat32_image.fs, &fat32_image.root_node.node.Dir, file_path.to, .CREATE_FILE, .{}); + const opened_file = &opened_node.File; + defer opened_file.close(); + + const orig_file = try std.fs.cwd().openFile(file_path.from, .{}); + defer orig_file.close(); + + // TODO: Might need to increase max size of files get too big. + const orig_content = try orig_file.readToEndAlloc(allocator, 10 * 1024 * 1024); + + _ = try opened_file.write(orig_content); + } + } + + /// + /// Create a FAT32 builder step. + /// + /// Argument: + /// IN builder: *Builder - The build builder. + /// IN options: Options - Options for creating FAT32 image. + /// IN stream: StreamType - The stream to write the FAT32 image to. + /// IN files: []const FromTo - The list of file paths to copy into the FAT32 image. + /// + /// Return: *Self + /// The FAT32 builder step pointer to add to the build process. + /// + pub fn create(builder: *Builder, options: makefs.Fat32.Options, stream: StreamType, files: []const FromTo) *Self { + const fat32_builder_step = builder.allocator.create(Self) catch unreachable; + fat32_builder_step.* = .{ + .step = Step.init(.Custom, builder.fmt("Fat32BuilderStep", .{}), builder.allocator, make), + .builder = builder, + .options = options, + .stream = stream, + .files = files, + }; + return fat32_builder_step; + } + }; +} /// The ramdisk make step for creating the initial ramdisk. const RamdiskStep = struct { @@ -305,7 +492,7 @@ const RamdiskStep = struct { out_file_path: []const u8, /// The possible errors for creating a ramdisk - const Error = (error{EndOfStream} || File.ReadError || File.SeekError || Allocator.Error || File.WriteError || File.OpenError); + const Error = (error{ EndOfStream, FileTooBig } || Allocator.Error || File.ReadError || File.GetSeekPosError || File.WriteError || File.OpenError); /// /// Create and write the files to a raw ramdisk in the format: diff --git a/echfs b/echfs deleted file mode 160000 index 14da9801..00000000 --- a/echfs +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 14da980137db24505cf48e3661b2b263dc7341dc diff --git a/makeiso_64.sh b/makeiso_64.sh index 17e69d10..21c348b9 100755 --- a/makeiso_64.sh +++ b/makeiso_64.sh @@ -1,20 +1,16 @@ #!/usr/bin/env bash -which readelf > /dev/null || exit_missing +MAP_FILE=$1 +PLUTO_ELF=$2 +OUTPUT_FILE=$3 +RAMDISK=$4 -INSTALL_PATH=$1 -BIN_PATH=$2 -PLUTO_ELF=$3 -OUTPUT_FILE=$4 -RAMDISK=$5 +exit_missing() { + printf "$_ must be installed\n"; + exit 1; +} -if [[ ! -f ./echfs/echfs-utils ]] -then - echo "Building echfs-utils" - cd echfs - make echfs-utils - cd .. -fi +which readelf > /dev/null || exit_missing if [[ ! -f ./limine/limine-install ]] then @@ -24,26 +20,6 @@ then cd .. fi -MAP_FILE=$INSTALL_PATH/"kernel.map" - # Read the symbols from the binary, remove all the unnecessary columns with awk and emit to a map file readelf -s --wide $PLUTO_ELF | grep -F "FUNC" | awk '{$1=$3=$4=$5=$6=$7=""; print $0}' | sort -k 1 > $MAP_FILE echo "" >> $MAP_FILE - -if [[ -f $OUTPUT_FILE ]] -then - rm $OUTPUT_FILE -fi - -mkdir -p $BIN_PATH - -dd if=/dev/zero bs=1M count=0 seek=64 of=$OUTPUT_FILE -parted -s $OUTPUT_FILE mklabel msdos -parted -s $OUTPUT_FILE mkpart primary 1 100% -./echfs/echfs-utils -m -p0 $OUTPUT_FILE quick-format 32768 -./echfs/echfs-utils -m -p0 $OUTPUT_FILE import limine.cfg limine.cfg -./echfs/echfs-utils -m -p0 $OUTPUT_FILE import limine/limine.sys limine.sys -./echfs/echfs-utils -m -p0 $OUTPUT_FILE import $PLUTO_ELF pluto.elf -./echfs/echfs-utils -m -p0 $OUTPUT_FILE import $RAMDISK initrd.ramdisk -./echfs/echfs-utils -m -p0 $OUTPUT_FILE import $MAP_FILE kernel.map -./limine/limine-install $OUTPUT_FILE diff --git a/src/kernel/arch/x86/64bit/boot.zig b/src/kernel/arch/x86/64bit/boot.zig index b016d884..659ebfed 100644 --- a/src/kernel/arch/x86/64bit/boot.zig +++ b/src/kernel/arch/x86/64bit/boot.zig @@ -6,7 +6,7 @@ const arch = @import("arch.zig"); const Header = packed struct { /// The kernel entry point or 0 for entry point of Elf file. entry_point: u64, - /// THe stack address. + /// The stack address. stack: *u8, /// Unused flags flags: u64, diff --git a/src/kernel/arch/x86/common/arch.zig b/src/kernel/arch/x86/common/arch.zig index 8d6264c3..f2138b90 100644 --- a/src/kernel/arch/x86/common/arch.zig +++ b/src/kernel/arch/x86/common/arch.zig @@ -1,9 +1,5 @@ const builtin = @import("builtin"); -const gdt = switch (builtin.arch) { - .i386 => @import("../32bit/gdt.zig"), - .x86_64 => @import("../64bit/gdt.zig"), - else => unreachable, -}; +const gdt = @import("gdt.zig"); const idt = switch (builtin.arch) { .i386 => @import("../32bit/idt.zig"), .x86_64 => @import("../64bit/idt.zig"), @@ -21,6 +17,91 @@ const BootPayload = switch (builtin.arch) { else => unreachable, }; +// This is the assembly for the FAT bootloader. +// [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 + +/// Basic boot code that will just print to the scream to insert a bootable image. This is intended +/// as a place holder to be over written with real boot code if needed. +/// This assumes the 512 sector size and includes the 0xAA55 boot signature. +pub const filesystem_bootsector_boot_code = [512]u8{ + 0xEB, 0x58, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, +}; + /// /// Assembly that reads data from a given port and returns its value. /// diff --git a/src/kernel/filesystem/fat32.zig b/src/kernel/filesystem/fat32.zig index 93f51452..9b55c81a 100644 --- a/src/kernel/filesystem/fat32.zig +++ b/src/kernel/filesystem/fat32.zig @@ -8,11 +8,12 @@ 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 time = @import("../time.zig"); const vfs = @import("vfs.zig"); const mem = @import("../mem.zig"); const CodePage = @import("../code_page/code_page.zig").CodePage; -const mkfat32 = @import("../../../mkfat32.zig"); +const makefs = @import("makefs.zig"); +const fs_common = @import("filesystem_common.zig"); /// The boot record for FAT32. This is use for parsing the initial boot sector to extract the /// relevant information for todays FAT32. @@ -557,34 +558,6 @@ fn initBytes(comptime Type: type, copy_struct: Type, bytes: []u8) void { } } -/// -/// A convenient function for returning the error types for reading, writing and seeking a stream. -/// -/// Arguments: -/// IN comptime StreamType: type - The stream to get the error set from. -/// -/// Return: type -/// The Error set for reading, writing and seeking the stream. -/// -fn ErrorSet(comptime StreamType: type) type { - const ReadError = switch (@typeInfo(StreamType)) { - .Pointer => |p| p.child.ReadError, - else => StreamType.ReadError, - }; - - const WriteError = switch (@typeInfo(StreamType)) { - .Pointer => |p| p.child.WriteError, - else => StreamType.WriteError, - }; - - const SeekError = switch (@typeInfo(StreamType)) { - .Pointer => |p| p.child.SeekError, - else => StreamType.SeekError, - }; - - return ReadError || WriteError || SeekError; -} - /// /// FAT32 filesystem. /// @@ -695,28 +668,16 @@ pub fn Fat32FS(comptime StreamType: type) type { // Can't directly access the fields of a pointer type, idk if this is a bug? /// The errors that can occur when reading from the stream. - const ReadError = switch (@typeInfo(StreamType)) { - .Pointer => |p| p.child.ReadError, - else => StreamType.ReadError, - }; + const ReadError = fs_common.GetReadError(StreamType); /// The errors that can occur when writing to the stream. - const WriteError = switch (@typeInfo(StreamType)) { - .Pointer => |p| p.child.WriteError, - else => StreamType.WriteError, - }; + const WriteError = fs_common.GetWriteError(StreamType); /// The errors that can occur when seeking the stream. - const SeekError = switch (@typeInfo(StreamType)) { - .Pointer => |p| p.child.SeekError, - else => StreamType.SeekError, - }; + const SeekError = fs_common.GetSeekError(StreamType); /// The errors that can occur when getting the seek position of the stream. - const GetPosError = switch (@typeInfo(StreamType)) { - .Pointer => |p| p.child.GetPosError, - else => StreamType.GetPosError, - }; + const GetPosError = fs_common.GetGetSeekPosError(StreamType); /// An iterator for looping over the cluster chain in the FAT and reading the cluster data. const ClusterChainIterator = struct { @@ -754,12 +715,12 @@ pub fn Fat32FS(comptime StreamType: type) type { /// Arguments: /// IN self: *ClusterChainIteratorSelf - Iterator self. /// - /// Error: Fat32Self.Error || ReadError || SeekError - /// Fat32Self.Error - If reading the stream didn't fill the cache FAT array. + /// Error: Error || ReadError || SeekError + /// Error - If reading the stream didn't fill the cache FAT array. /// ReadError - If there is an error reading from the stream. /// SeekError - If there is an error seeking the stream. /// - fn checkRead(self: *ClusterChainIteratorSelf) (Fat32Self.Error || ReadError || SeekError)!void { + fn checkRead(self: *ClusterChainIteratorSelf) (Error || ReadError || SeekError)!void { if (self.cluster_offset >= self.fat_config.bytes_per_sector * self.fat_config.sectors_per_cluster) { self.cluster = self.fat[self.cluster - self.table_offset]; self.cluster_offset = 0; @@ -774,7 +735,7 @@ pub fn Fat32FS(comptime StreamType: type) type { try self.stream.seekableStream().seekTo((self.fat_config.reserved_sectors + self.table_offset) * self.fat_config.bytes_per_sector); const read_count = try self.stream.reader().readAll(std.mem.sliceAsBytes(self.fat)); if (read_count != self.fat.len * @sizeOf(u32)) { - return Fat32Self.Error.BadRead; + return Error.BadRead; } } } @@ -796,12 +757,12 @@ pub fn Fat32FS(comptime StreamType: type) type { /// The end index into the buffer where the next read should start. If returned /// null, then the buffer is full or the end of the cluster chain is reached. /// - /// Error: Fat32Self.Error || ReadError || SeekError - /// Fat32Self.Error - (BadRead) If the buffer isn't aligned to the bytes per cluster boundary. + /// Error: Error || ReadError || SeekError + /// Error - (BadRead) If the buffer isn't aligned to the bytes per cluster boundary. /// ReadError - If there is an error reading from the stream. /// SeekError - If there is an error seeking the stream. /// - pub fn read(self: *ClusterChainIteratorSelf, buff: []u8) (Fat32Self.Error || ReadError || SeekError)!?u32 { + pub fn read(self: *ClusterChainIteratorSelf, buff: []u8) (Error || ReadError || SeekError)!?u32 { // FAT32 is really FAT28, so the top 4 bits are not used, so mask them out if (buff.len != 0 and self.cluster != 0 and (self.cluster & 0x0FFFFFFF) < self.fat_config.cluster_end_marker) { // Seek to the sector where the cluster is @@ -816,7 +777,7 @@ pub fn Fat32FS(comptime StreamType: type) type { // So would read from the cache rather than the stream itself. const read_count = try self.stream.reader().readAll(buff[0..read_len]); if (read_count != read_len) { - return Fat32Self.Error.BadRead; + return Error.BadRead; } // Increment the cluster @@ -850,13 +811,13 @@ pub fn Fat32FS(comptime StreamType: type) type { /// Return: ClusterChainIteratorSelf /// A cluster chain iterator. /// - /// Error: Allocator.Error || Fat32Self.Error || ReadError || SeekError + /// Error: Allocator.Error || Error || ReadError || SeekError /// Allocator.Error - If there is an error allocating the initial FAT cache. - /// Fat32Self.Error - If reading the stream didn't fill the cache FAT array. + /// Error - If reading the stream didn't fill the cache FAT array. /// ReadError - If there is an error reading from the stream. /// SeekError - If there is an error seeking the stream. /// - pub fn init(allocator: *Allocator, fat_config: FATConfig, cluster: u32, stream: StreamType) (Allocator.Error || Fat32Self.Error || ReadError || SeekError)!ClusterChainIteratorSelf { + pub fn init(allocator: *Allocator, fat_config: FATConfig, cluster: u32, stream: StreamType) (Allocator.Error || Error || ReadError || SeekError)!ClusterChainIteratorSelf { // Create a bytes per sector sized cache of the FAT. var fat = try allocator.alloc(u32, fat_config.bytes_per_sector / @sizeOf(u32)); errdefer allocator.free(fat); @@ -868,7 +829,7 @@ pub fn Fat32FS(comptime StreamType: type) type { try stream.seekableStream().seekTo((fat_config.reserved_sectors + table_offset) * fat_config.bytes_per_sector); const read_count = try stream.reader().readAll(std.mem.sliceAsBytes(fat)); if (read_count != fat.len * @sizeOf(u32)) { - return Fat32Self.Error.BadRead; + return Error.BadRead; } return ClusterChainIteratorSelf{ @@ -943,13 +904,13 @@ pub fn Fat32FS(comptime StreamType: type) type { /// Arguments: /// IN self: *EntryIteratorSelf - Iterator self. /// - /// Error: Fat32Self.Error || ReadError || SeekError || EntryItError - /// Fat32Self.Error - Error reading the cluster chain if the buffer isn't aligned. + /// Error: Error || ReadError || SeekError || EntryItError + /// Error - Error reading the cluster chain if the buffer isn't aligned. /// ReadError - Error reading from the stream in the cluster iterator. /// SeekError - Error seeking the stream in the cluster iterator. /// error.EndClusterChain - Reading the next cluster and reach the end unexpectedly. /// - fn checkRead(self: *EntryIteratorSelf) (Fat32Self.Error || ReadError || SeekError || EntryItError)!void { + fn checkRead(self: *EntryIteratorSelf) (Error || ReadError || SeekError || EntryItError)!void { if (self.index >= self.cluster_block.len) { // Read the next block var index: u32 = 0; @@ -978,9 +939,9 @@ pub fn Fat32FS(comptime StreamType: type) type { /// Return: ?Entry /// The FAT entry. Will return null if there are no more entries for the directory. /// - /// Error: Allocator.Error || Fat32Self.Error || ReadError || SeekError || EntryItError + /// Error: Allocator.Error || Error || ReadError || SeekError || EntryItError /// Allocator.Error - Error allocating memory for fields in the return entry. - /// Fat32Self.Error - Error reading the cluster chain if the buffer isn't aligned. + /// Error - Error reading the cluster chain if the buffer isn't aligned. /// ReadError - Error reading from the underlying stream. /// SeekError - Error seeking the underlying stream. /// error.Orphan - If there is a long entry without a short name entry or the @@ -988,7 +949,7 @@ pub fn Fat32FS(comptime StreamType: type) type { /// short entry or the long entry is missing entry parts. /// error.EndClusterChain - Reading the next cluster and reach the end unexpectedly. /// - fn nextImp(self: *EntryIteratorSelf) (Allocator.Error || Fat32Self.Error || ReadError || SeekError || LongName.Error || EntryItError)!?Entry { + fn nextImp(self: *EntryIteratorSelf) (Allocator.Error || Error || ReadError || SeekError || LongName.Error || EntryItError)!?Entry { // Do we need to read the next block try self.checkRead(); @@ -1088,13 +1049,13 @@ pub fn Fat32FS(comptime StreamType: type) type { /// The FAT entry. Will return null if there are no more entries for the directory. /// The entry must be free using the deinit() function. /// - /// Error: Allocator.Error || Fat32Self.Error || ReadError || SeekError + /// Error: Allocator.Error || Error || ReadError || SeekError /// Allocator.Error - Error allocating memory for fields in the return entry. - /// Fat32Self.Error - Error reading the cluster chain if the buffer isn't aligned. + /// Error - Error reading the cluster chain if the buffer isn't aligned. /// ReadError - Error reading from the underlying stream. /// SeekError - Error seeking the underlying stream. /// - pub fn next(self: *EntryIteratorSelf) (Allocator.Error || Fat32Self.Error || ReadError || SeekError || LongName.Error)!?Entry { + pub fn next(self: *EntryIteratorSelf) (Allocator.Error || Error || ReadError || SeekError || LongName.Error)!?Entry { // If there is a orphan file, then just get the next one // If we hit the end of the cluster chain, return null while (true) { @@ -1103,7 +1064,7 @@ pub fn Fat32FS(comptime StreamType: type) type { } else |e| switch (e) { error.Orphan => continue, error.EndClusterChain => return null, - else => return @errSetCast(Allocator.Error || Fat32Self.Error || ReadError || SeekError || LongName.Error, e), + else => return @errSetCast(Allocator.Error || Error || ReadError || SeekError || LongName.Error, e), } } } @@ -1132,13 +1093,13 @@ pub fn Fat32FS(comptime StreamType: type) type { /// Return: EntryIteratorSelf /// The entry iterator. /// - /// Error: Allocator.Error || Fat32Self.Error || ReadError || SeekError + /// Error: Allocator.Error || Error || ReadError || SeekError /// Allocator.Error - Error allocating memory for fields in the return entry. - /// Fat32Self.Error - Error reading the cluster chain if the buffer isn't aligned. + /// Error - Error reading the cluster chain if the buffer isn't aligned. /// ReadError - Error reading from the underlying stream. /// SeekError - Error seeking the underlying stream. /// - pub fn init(allocator: *Allocator, fat_config: FATConfig, cluster: u32, stream: StreamType) (Allocator.Error || Fat32Self.Error || ReadError || SeekError)!EntryIteratorSelf { + pub fn init(allocator: *Allocator, fat_config: FATConfig, cluster: u32, stream: StreamType) (Allocator.Error || Error || ReadError || SeekError)!EntryIteratorSelf { var cluster_block = try allocator.alloc(u8, fat_config.bytes_per_sector * fat_config.sectors_per_cluster); errdefer allocator.free(cluster_block); var it = try ClusterChainIterator.init(allocator, fat_config, cluster, stream); @@ -1398,15 +1359,15 @@ pub fn Fat32FS(comptime StreamType: type) type { /// Return: u32 /// The next free cluster to use. /// - /// Error: Allocator.Error || ReadError || SeekError || Fat32Self.Error + /// Error: Allocator.Error || ReadError || SeekError || 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. + /// Error.BadRead - Error reading the FAT, not aligned to the sector. + /// Error.DiskFull - No free clusters. /// - fn findNextFreeCluster(self: *Fat32Self, cluster_hint: u32, parent_cluster: ?u32) (Allocator.Error || WriteError || ReadError || SeekError || Fat32Self.Error)!u32 { + fn findNextFreeCluster(self: *Fat32Self, cluster_hint: u32, parent_cluster: ?u32) (Allocator.Error || WriteError || ReadError || SeekError || 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; @@ -1418,7 +1379,7 @@ pub fn Fat32FS(comptime StreamType: type) type { 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; + return Error.BadRead; } // Check for a free cluster by checking the FAT for a 0x00000000 entry (free) @@ -1435,13 +1396,13 @@ pub fn Fat32FS(comptime StreamType: type) type { if (self.fat_config.has_fs_info) { std.debug.assert(self.fat_config.number_free_clusters == 0); } - return Fat32Self.Error.DiskFull; + return 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; + return Error.BadRead; } } } @@ -1593,11 +1554,11 @@ pub fn Fat32FS(comptime StreamType: type) type { /// Return: FatDirEntry /// The full FAT entry ready to be copies to disk, byte by byte. /// - /// Error: Allocator.Error || Fat32Self.Error + /// Error: Allocator.Error || Error /// Allocator.Error - Error allocating memory. - /// Fat32Self.Error - The name provided cannot be converted to a valid FAT32 name. + /// 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 { + fn createEntries(allocator: *Allocator, name: []const u8, cluster: u32, attributes: ShortName.Attributes, existing_short_names: []const [11]u8) (Allocator.Error || Error)!FatDirEntry { const long_name = try nameToLongName(allocator, name); defer allocator.free(long_name); const short_name = try longNameToShortName(long_name, existing_short_names); @@ -1620,11 +1581,11 @@ pub fn Fat32FS(comptime StreamType: type) type { /// 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. + /// Error: Allocator.Error || Error + /// Allocator.Error - Error allocating memory. + /// 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 { + fn nameToLongName(allocator: *Allocator, name: []const u8) (Allocator.Error || 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); @@ -1632,7 +1593,7 @@ pub fn Fat32FS(comptime StreamType: type) type { // 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; + const utf8_view = std.unicode.Utf8View.init(name) catch return Error.InvalidName; var utf8_it = utf8_view.iterator(); // Make sure the code points as valid for the long name var ignored_leading = false; @@ -1645,14 +1606,14 @@ pub fn Fat32FS(comptime StreamType: type) type { // 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; + return Error.InvalidName; } // Check for invalid characters const invalid_chars = "\"*/:<>?\\|"; inline for (invalid_chars) |char| { if (char == code_point) { - return Fat32Self.Error.InvalidName; + return Error.InvalidName; } } @@ -1667,7 +1628,7 @@ pub fn Fat32FS(comptime StreamType: type) type { // Check the generated name is a valid length if (long_name.len > 255) { - return Fat32Self.Error.InvalidName; + return Error.InvalidName; } return long_name; } @@ -1719,10 +1680,10 @@ pub fn Fat32FS(comptime StreamType: type) type { /// Return: [11]u8 /// The converted short name. /// - /// Error: Fat32Self.Error - /// Fat32Self.Error.InvalidName - If the directory is fill of the same short file name. + /// Error: Error + /// 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 { + fn longNameToShortName(long_name: []const u16, existing_names: []const [11]u8) Error![11]u8 { // Pad with spaces var sfn: [11]u8 = [_]u8{' '} ** 11; var sfn_i: u8 = 0; @@ -1946,22 +1907,22 @@ pub fn Fat32FS(comptime StreamType: type) type { /// 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_time = time.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); + const dt_date = @intCast(u16, date_time.day | date_time.month << 5 | (date_time.year - 1980) << 9); + const dt_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, + .time_created = dt_time, + .date_created = dt_date, + .date_last_access = dt_date, .cluster_high = @truncate(u16, cluster >> 16), - .time_last_modification = time, - .date_last_modification = date, + .time_last_modification = dt_time, + .date_last_modification = dt_date, .cluster_low = @truncate(u16, cluster), .size = 0x00000000, }; @@ -1990,9 +1951,9 @@ pub fn Fat32FS(comptime StreamType: type) type { /// 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. + /// 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 } { + fn writeEntries(self: *Fat32Self, entries: FatDirEntry, at_cluster: u32, next_free_cluster_hint: u32, initial_cluster_offset: u32) (Allocator.Error || WriteError || ReadError || SeekError || 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); @@ -2047,15 +2008,19 @@ pub fn Fat32FS(comptime StreamType: type) type { /// /// Deinitialise this file system. This frees the root node, virtual filesystem and self. - /// This asserts that there are no open files left. + /// This asserts that there are no open files left and will return an error if there are. + /// If an error is returned, then the the filesystem is still valid. /// /// Arguments: /// IN self: *Fat32Self - Self to free. /// - pub fn destroy(self: *Fat32Self) Fat32Self.Error!void { + /// Error: Error + /// Error.FilesStillOpen - There are files still open. + /// + pub fn destroy(self: *Fat32Self) Error!void { // Make sure we have closed all files if (self.opened_files.count() != 0) { - return Fat32Self.Error.FilesStillOpen; + return Error.FilesStillOpen; } self.opened_files.deinit(); self.allocator.destroy(self.root_node.node); @@ -2073,14 +2038,14 @@ pub fn Fat32FS(comptime StreamType: type) type { /// Return: *Fat32 /// The pointer to a FAT32 filesystem. /// - /// Error: Allocator.Error || ReadError || SeekError || Fat32Self.Error + /// Error: Allocator.Error || ReadError || SeekError || Error /// Allocator.Error - If there is no more memory. Any memory allocated will be freed. /// ReadError - If there is an error reading from the stream. /// SeekError - If there si an error seeking the stream. - /// Fat32Self.Error - If there is an error when parsing the stream to set up a fAT32 + /// Error - If there is an error when parsing the stream to set up a fAT32 /// filesystem. See Error for the list of possible errors. /// - pub fn create(allocator: *Allocator, stream: StreamType) (Allocator.Error || ReadError || SeekError || Fat32Self.Error)!*Fat32Self { + pub fn create(allocator: *Allocator, stream: StreamType) (Allocator.Error || ReadError || SeekError || Error)!*Fat32Self { log.debug("Init\n", .{}); defer log.debug("Done\n", .{}); // We need to get the root directory sector. For this we need to read the boot sector. @@ -2252,15 +2217,17 @@ pub fn Fat32FS(comptime StreamType: type) type { /// Return: *Fat32FS(@TypeOf(stream)) /// A pointer to a FAT32 filesystem. /// -/// Error: Allocator.Error || Fat32FS(@TypeOf(stream)).Error +/// Error: Allocator.Error || ReadError || SeekError /// Allocator.Error - If there isn't enough memory to create the filesystem. +/// ReadError - Error reading from the stream. +/// SeekError - Error seeking the stream. /// -pub fn initialiseFAT32(allocator: *Allocator, stream: anytype) (Allocator.Error || ErrorSet(@TypeOf(stream)) || Fat32FS(@TypeOf(stream)).Error)!*Fat32FS(@TypeOf(stream)) { +pub fn initialiseFAT32(allocator: *Allocator, stream: anytype) (Allocator.Error || Fat32FS(@TypeOf(stream)).ReadError || Fat32FS(@TypeOf(stream)).SeekError || Fat32FS(@TypeOf(stream)).Error)!*Fat32FS(@TypeOf(stream)) { return Fat32FS(@TypeOf(stream)).create(allocator, stream); } /// -/// Create a test FAT32 filesystem. This will use mkfat32 to create the temporary FAT32 then the +/// Create a test FAT32 filesystem. This will use makefs.FAT32 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. /// @@ -2283,7 +2250,7 @@ fn testFAT32FS(allocator: *Allocator, stream: anytype, fat_config: FATConfig) an var temp_stream = &std.io.fixedBufferStream(test_file_buf[0..]); - try mkfat32.Fat32.make(.{ .image_size = @intCast(u32, test_file_buf.len) }, temp_stream, true); + try makefs.Fat32.make(.{ .image_size = @intCast(u32, test_file_buf.len), .quick_format = true }, temp_stream); var test_fs = try initialiseFAT32(std.testing.allocator, temp_stream); test_fs.stream = stream; @@ -4019,7 +3986,7 @@ test "Fat32FS.open - no create - hand crafted" { var stream = &std.io.fixedBufferStream(test_file_buf[0..]); - try mkfat32.Fat32.make(.{ .image_size = @intCast(u32, test_file_buf.len) }, stream, true); + try makefs.Fat32.make(.{ .image_size = @intCast(u32, test_file_buf.len), .quick_format = true }, stream); var test_fs = try initialiseFAT32(std.testing.allocator, stream); defer test_fs.destroy() catch unreachable; @@ -4104,7 +4071,7 @@ test "Fat32FS.open - create file" { var stream = &std.io.fixedBufferStream(test_file_buf[0..]); - try mkfat32.Fat32.make(.{ .image_size = @intCast(u32, test_file_buf.len) }, stream, true); + try makefs.Fat32.make(.{ .image_size = @intCast(u32, test_file_buf.len), .quick_format = true }, stream); var test_fs = try initialiseFAT32(std.testing.allocator, stream); defer test_fs.destroy() catch unreachable; @@ -4135,7 +4102,7 @@ test "Fat32FS.open - create directory" { var stream = &std.io.fixedBufferStream(test_file_buf[0..]); - try mkfat32.Fat32.make(.{ .image_size = @intCast(u32, test_file_buf.len) }, stream, true); + try makefs.Fat32.make(.{ .image_size = @intCast(u32, test_file_buf.len), .quick_format = true }, stream); var test_fs = try initialiseFAT32(std.testing.allocator, stream); defer test_fs.destroy() catch unreachable; @@ -4159,7 +4126,7 @@ test "Fat32FS.open - create symlink" { var stream = &std.io.fixedBufferStream(test_file_buf[0..]); - try mkfat32.Fat32.make(.{ .image_size = @intCast(u32, test_file_buf.len) }, stream, true); + try makefs.Fat32.make(.{ .image_size = @intCast(u32, test_file_buf.len), .quick_format = true }, stream); var test_fs = try initialiseFAT32(std.testing.allocator, stream); defer test_fs.destroy() catch unreachable; @@ -4175,7 +4142,7 @@ test "Fat32FS.open - create nested directories" { var stream = &std.io.fixedBufferStream(test_file_buf[0..]); - try mkfat32.Fat32.make(.{ .image_size = @intCast(u32, test_file_buf.len) }, stream, true); + try makefs.Fat32.make(.{ .image_size = @intCast(u32, test_file_buf.len), .quick_format = true }, stream); var test_fs = try initialiseFAT32(std.testing.allocator, stream); defer test_fs.destroy() catch unreachable; @@ -5688,7 +5655,7 @@ test "Fat32FS.write - small file" { var stream = &std.io.fixedBufferStream(test_file_buf[0..]); - try mkfat32.Fat32.make(.{ .image_size = @intCast(u32, test_file_buf.len) }, stream, false); + try makefs.Fat32.make(.{ .image_size = @intCast(u32, test_file_buf.len) }, stream); var test_fs = try initialiseFAT32(std.testing.allocator, stream); defer test_fs.destroy() catch unreachable; @@ -5724,7 +5691,7 @@ test "Fat32FS.write - large file" { var stream = &std.io.fixedBufferStream(test_file_buf[0..]); - try mkfat32.Fat32.make(.{ .image_size = @intCast(u32, test_file_buf.len) }, stream, false); + try makefs.Fat32.make(.{ .image_size = @intCast(u32, test_file_buf.len) }, stream); var test_fs = try initialiseFAT32(std.testing.allocator, stream); defer test_fs.destroy() catch unreachable; @@ -5816,7 +5783,7 @@ test "Fat32FS.write - test files" { var stream = &std.io.fixedBufferStream(test_file_buf[0..]); - try mkfat32.Fat32.make(.{ .image_size = @intCast(u32, test_file_buf.len) }, stream, false); + try makefs.Fat32.make(.{ .image_size = @intCast(u32, test_file_buf.len) }, stream); var test_fs = try initialiseFAT32(std.testing.allocator, stream); defer test_fs.destroy() catch unreachable; @@ -5839,7 +5806,7 @@ test "Fat32FS.write - not enough space" { var stream = &std.io.fixedBufferStream(test_file_buf[0..]); - try mkfat32.Fat32.make(.{ .image_size = @intCast(u32, test_file_buf.len) }, stream, false); + try makefs.Fat32.make(.{ .image_size = @intCast(u32, test_file_buf.len) }, stream); var test_fs = try initialiseFAT32(std.testing.allocator, stream); defer test_fs.destroy() catch unreachable; @@ -6009,7 +5976,7 @@ test "Fat32FS.init FATConfig mix FSInfo" { 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 + // This is the default config that should be produced from makefs.Fat32 const expected = FATConfig{ .bytes_per_sector = 512, .sectors_per_cluster = 1, @@ -6038,7 +6005,7 @@ test "Fat32FS.init FATConfig mix FSInfo" { 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 + // This is the default config that should be produced from makefs.Fat32 const expected = FATConfig{ .bytes_per_sector = 512, .sectors_per_cluster = 1, @@ -6070,7 +6037,7 @@ test "Fat32FS.init FATConfig mix FSInfo" { 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 + // This is the default config that should be produced from makefs.Fat32 const expected = FATConfig{ .bytes_per_sector = 512, .sectors_per_cluster = 1, diff --git a/src/kernel/filesystem/filesystem_common.zig b/src/kernel/filesystem/filesystem_common.zig new file mode 100644 index 00000000..710eb18d --- /dev/null +++ b/src/kernel/filesystem/filesystem_common.zig @@ -0,0 +1,90 @@ +const std = @import("std"); + +pub const filesystem_bootsector_boot_code = @import("../arch.zig").internals.filesystem_bootsector_boot_code; + +/// +/// A convenient function for returning the error type for reading from the stream. +/// +/// Arguments: +/// IN comptime StreamType: type - The stream to get the error set from. +/// +/// Return: type +/// The Error set for reading. +/// +pub fn GetReadError(comptime StreamType: type) type { + return switch (@typeInfo(StreamType)) { + .Pointer => |p| p.child.ReadError, + else => StreamType.ReadError, + }; +} + +/// +/// A convenient function for returning the error type for writing from the stream. +/// +/// Arguments: +/// IN comptime StreamType: type - The stream to get the error set from. +/// +/// Return: type +/// The Error set for writing. +/// +pub fn GetWriteError(comptime StreamType: type) type { + return switch (@typeInfo(StreamType)) { + .Pointer => |p| p.child.WriteError, + else => StreamType.WriteError, + }; +} + +/// +/// A convenient function for returning the error type for seeking from the stream. +/// +/// Arguments: +/// IN comptime StreamType: type - The stream to get the error set from. +/// +/// Return: type +/// The Error set for seeking. +/// +pub fn GetSeekError(comptime StreamType: type) type { + return switch (@typeInfo(StreamType)) { + .Pointer => |p| p.child.SeekError, + else => StreamType.SeekError, + }; +} + +/// +/// A convenient function for returning the error type for get seek position from the stream. +/// +/// Arguments: +/// IN comptime StreamType: type - The stream to get the error set from. +/// +/// Return: type +/// The Error set for get seek position. +/// +pub fn GetGetSeekPosError(comptime StreamType: type) type { + return switch (@typeInfo(StreamType)) { + .Pointer => |p| p.child.GetSeekPosError, + else => StreamType.GetSeekPosError, + }; +} + +/// +/// Clear the stream. This is the same as writeByteNTimes but with a bigger buffer (4096 bytes). +/// This improves performance a lot. +/// +/// Arguments: +/// IN stream: anytype - The stream to clear. +/// IN size: usize - The size to clear from where the position is in the stream. +/// +/// Error: @TypeOf(stream).WriteError +/// @TypeOf(stream).WriteError - Error writing to the stream. +/// +pub fn clearStream(stream: anytype, size: usize) GetWriteError(@TypeOf(stream))!void { + comptime const buff_size = 4096; + comptime const bytes: [buff_size]u8 = [_]u8{0x00} ** buff_size; + + var remaining: usize = size; + while (remaining > 0) { + const to_write = std.math.min(remaining, bytes.len); + try stream.writer().writeAll(bytes[0..to_write]); + remaining -= to_write; + } +} diff --git a/mkfat32.zig b/src/kernel/filesystem/makefs.zig similarity index 71% rename from mkfat32.zig rename to src/kernel/filesystem/makefs.zig index 22f7ea3d..2e9e8ca4 100644 --- a/mkfat32.zig +++ b/src/kernel/filesystem/makefs.zig @@ -1,52 +1,8 @@ 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 +const expectError = std.testing.expectError; +const builtin = @import("builtin"); +const fs_common = @import("filesystem_common.zig"); +const mbr_driver = @import("mbr.zig"); /// A FAT32 static structure for creating a empty FAT32 image. This contains helper functions /// for creating a empty FAT32 image. @@ -177,14 +133,18 @@ pub const Fat32 = struct { /// The formatted volume name. Volume names shorter than 11 characters must have trailing /// spaces. Default NO MANE. volume_name: [11]u8 = getDefaultVolumeName(), + + /// Whether to quick format the filesystem. Whether to completely zero the stream + /// initially or zero just the important sectors. + quick_format: bool = false, }; /// 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. + /// If the FAT image that the user wants to create is too small: < 17.5KB. TooSmall, - /// If the FAT image that the user want's to create is too large: > 2TB. + /// If the FAT image that the user wants to create is too large: > 2TB. TooLarge, /// The value in a option provided from the user is invalid. @@ -194,65 +154,6 @@ pub const Fat32 = struct { /// 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. @@ -386,7 +287,7 @@ pub const Fat32 = struct { /// @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 { + fn writeFSInfo(stream: anytype, fat32_header: Header, free_cluster_num: u32, next_free_cluster: u32) (fs_common.GetSeekError(@TypeOf(stream)) || fs_common.GetWriteError(@TypeOf(stream)))!void { const seekable_stream = stream.seekableStream(); const writer = stream.writer(); @@ -437,7 +338,7 @@ pub const Fat32 = struct { /// @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 { + fn writeFAT(stream: anytype, fat32_header: Header) (fs_common.GetSeekError(@TypeOf(stream)) || fs_common.GetWriteError(@TypeOf(stream)))!void { const seekable_stream = stream.seekableStream(); const writer = stream.writer(); @@ -471,12 +372,12 @@ pub const Fat32 = struct { /// @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 { + fn writeBootSector(stream: anytype, fat32_header: Header) (fs_common.GetSeekError(@TypeOf(stream)) || fs_common.GetWriteError(@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); + std.mem.copy(u8, &boot_sector, &fs_common.filesystem_bootsector_boot_code); // Write the header into the boot sector variable var fat32_header_stream = std.io.fixedBufferStream(boot_sector[3..90]); @@ -555,41 +456,16 @@ pub const Fat32 = struct { }; } - /// - /// Clear the stream. This is the same as writeByteNTimes but with a bigger buffer (4096 bytes). - /// This improves performance a lot. - /// - /// Arguments: - /// IN stream: anytype - The stream to clear. - /// IN size: usize - The size to clear from the beginning - /// - /// Error: @TypeOf(stream).WriteError - /// @TypeOf(stream).WriteError - Error writing to the stream. - /// - fn clearStream(stream: anytype, size: usize) ErrorSet(@TypeOf(stream))!void { - comptime const buff_size = 4096; - comptime const bytes: [buff_size]u8 = [_]u8{0x00} ** buff_size; - - var remaining: usize = size; - while (remaining > 0) { - const to_write = std.math.min(remaining, bytes.len); - try stream.writer().writeAll(bytes[0..to_write]); - remaining -= to_write; - } - } - /// /// 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. + /// 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. @@ -598,18 +474,23 @@ pub const Fat32 = struct { /// 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 { + pub fn make(options: Options, stream: anytype) (fs_common.GetSeekError(@TypeOf(stream)) || fs_common.GetWriteError(@TypeOf(stream)) || Error)!void { + // Don't print when testing as it floods the terminal + if (options.image_size < getDefaultImageSize() and !builtin.is_test) { + log.warn("Image size smaller than default. Got: 0x{X}, default: 0x{X}\n", .{ options.image_size, getDefaultImageSize() }); + } + // 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) { + if (options.quick_format) { // Zero just the reserved and FAT sectors - try clearStream(stream, (fat32_header.reserved_sectors + (fat32_header.sectors_per_fat * 2)) * fat32_header.bytes_per_sector); + try fs_common.clearStream(stream, (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 clearStream(stream, image_size); + try fs_common.clearStream(stream, image_size); } // Write the boot sector with the bootstrap code and header and the backup boot sector. @@ -626,3 +507,171 @@ pub const Fat32 = struct { try Fat32.writeFSInfo(stream, fat32_header, usable_clusters, 2); } }; + +pub const MBRPartition = struct { + /// The MBR make options + pub const Options = struct { + /// The options for the partition table as percentages. The total should not exceed 100%. + partition_options: [4]?u16 = .{ null, null, null, null }, + + /// The total image size to create. + image_size: u64, + }; + + /// The error set for making a new MBR partition scheme. + const Error = error{ + /// The start position of the partition stream is past the end of the main stream. + InvalidStart, + + /// The percentage of given is either to high (above 100) or the accumulative partition + /// percentages of all partitions exceeds 100. + InvalidPercentage, + + /// The size of the stream is invalid, i.e. too small. + InvalidSize, + }; + + /// The sector size of the drive. Unfortunately MBR assumes 512 bytes. + const SECTOR_SIZE: u32 = 512; + + /// The log function for printing errors when creating a FAT32 image. + const log = std.log.scoped(.mkmbr); + + /// + /// Write the partition table to the stream from the provided options. + /// + /// Arguments: + /// IN partition_num: u2 - The partition to write. + /// IN stream: StreamType - The stream to write the partition to. + /// IN size_percent: u16 - The size of stream to partition for this partition in a percentage. + /// IN start_pos: u32 - The start position to start the partition in sectors. + /// IN end_of_stream: u32 - The end of the stream in bytes. + /// + /// Return: u32 + /// The next sector after the end of the partition created. + /// + /// Error: Error || StreamType.WriteError || StreamType.SeekError + /// Error - The percentage of the stream is too large (> 100) or the + // start of the partition is past the end of the stream. + /// StreamType.WriteError - Error writing ot the stream. + /// StreamType.SeekError - Error seeking the stream. + /// + fn writePartition(partition_num: u2, stream: anytype, size_percent: u16, start_pos: u32, end_of_stream: u64) (Error || fs_common.GetSeekError(@TypeOf(stream)) || fs_common.GetWriteError(@TypeOf(stream)))!u32 { + // The offset of where the boot code finishes and partition table starts + const boot_code_end_offset = 446; + + if (size_percent > 100) { + return Error.InvalidPercentage; + } + + const seek_stream = stream.seekableStream(); + const writer_stream = stream.writer(); + + if (start_pos * SECTOR_SIZE > end_of_stream) { + return Error.InvalidStart; + } + + // Round up the partition size in number of sectors + const partition_size: u32 = @intCast(u32, ((end_of_stream - (start_pos * SECTOR_SIZE)) * (size_percent / 100) + (SECTOR_SIZE - 1)) / SECTOR_SIZE); + + // Seek to the offset to write the partition table + const partition_offset = @as(u32, partition_num) * @sizeOf(mbr_driver.Partition) + boot_code_end_offset; + try seek_stream.seekTo(partition_offset); + + // TODO: Add the CHS values, These seem to not be needed as can still mount in Linux + // But should be set at some point + const partition = mbr_driver.Partition{ + .drive_attributes = 0x80, + .chs_address_start1 = undefined, + .chs_address_start2 = undefined, + .chs_address_start3 = undefined, + .partition_type = 0x83, + .chs_address_end1 = undefined, + .chs_address_end2 = undefined, + .chs_address_end3 = undefined, + .lba_start = start_pos, + .number_of_sectors = partition_size, + }; + + const partition_bytes = @ptrCast([*]const u8, &partition)[0..16]; + try writer_stream.writeAll(partition_bytes); + return partition_size + 1; + } + + /// + /// Get the size of the reserved bytes at the beginning of the stream. This is set to 1MB + /// as this is reserved for the bootloader code. + /// + /// Return: u32 + /// The number of bytes reserved at the beginning of the stream. + /// + pub fn getReservedStartSize() u32 { + return 1024 * 1024; + } + + /// + /// Create a new MBR partition scheme on the provided stream. Any error returned will + /// result in the stream being left in a undefined state. This will clear the stream + /// and fill with all zeros based on the image size in the options. + /// + /// Arguments: + /// IN options: Options - The options for creating the MBR partition scheme. + /// IN stream: anytype - The stream to write the partition table to. + /// + /// Error: Error || StreamType.WriteError || StreamType.SeekError + /// Error - The image size is too small or there was an error writing + /// to the stream. Or the total percentage exceeds 100%. + /// StreamType.WriteError - Error writing ot the stream. + /// StreamType.SeekError - Error seeking the stream. + /// + pub fn make(options: Options, stream: anytype) (Error || fs_common.GetSeekError(@TypeOf(stream)) || fs_common.GetWriteError(@TypeOf(stream)))!void { + if (options.image_size < getReservedStartSize()) { + return Error.InvalidSize; + } + + // Clear/make the image the size given in the options + try fs_common.clearStream(stream, options.image_size); + + const seek_stream = stream.seekableStream(); + const writer_stream = stream.writer(); + + try seek_stream.seekTo(0); + try writer_stream.writeAll(fs_common.filesystem_bootsector_boot_code[0..]); + + // First MB is for the boot code + var next_start_sector_pos: u32 = getReservedStartSize() / SECTOR_SIZE; + + var acc_size: u16 = 0; + for (options.partition_options) |null_part_option, i| { + if (null_part_option) |size_percent| { + if (size_percent > 100 or acc_size > 100 - size_percent) { + return Error.InvalidPercentage; + } + acc_size += size_percent; + next_start_sector_pos = try writePartition(@intCast(u2, i), stream, size_percent, next_start_sector_pos, options.image_size); + } + } + } +}; + +test "MBRPartition.make - more than 100% for one partition" { + const allocator = std.testing.allocator; + + const size = 1024 * 1024 * 10; + var buffer = try allocator.alloc(u8, size); + defer allocator.free(buffer); + + const stream = &std.io.fixedBufferStream(buffer); + expectError(error.InvalidPercentage, MBRPartition.make(.{ .partition_options = .{ 101, null, null, null }, .image_size = size }, stream)); +} + +test "MBRPartition.make - more than 100% accumulative" { + const allocator = std.testing.allocator; + + const size = 1024 * 1024 * 10; + var buffer = try allocator.alloc(u8, size); + defer allocator.free(buffer); + + const stream = &std.io.fixedBufferStream(buffer); + expectError(error.InvalidPercentage, MBRPartition.make(.{ .partition_options = .{ 50, 25, 25, 1 }, .image_size = size }, stream)); +} diff --git a/src/kernel/filesystem/mbr.zig b/src/kernel/filesystem/mbr.zig new file mode 100644 index 00000000..cac2a9b5 --- /dev/null +++ b/src/kernel/filesystem/mbr.zig @@ -0,0 +1,408 @@ +const std = @import("std"); +const log = std.log.scoped(.mbr); +const Allocator = std.mem.Allocator; +const fs_common = @import("filesystem_common.zig"); +const expectError = std.testing.expectError; + +/// The first sector of a drive containing +const MBR = packed struct { + /// The boot code + bootstrap: [440]u8, + + /// Optional disk ID. This is unique for disks plugged into the PC. + unique_disk_id: u32, + + /// Optional read only flag. Normally 0x000 but 0x5A5A to indicate read only. + read_only: u16 = 0x000, + + /// The first partition structure. + partition_1: Partition, + + /// The second partition structure. + partition_2: Partition, + + /// The third partition structure. + partition_3: Partition, + + /// The fourth partition structure. + partition_4: Partition, + + /// The boot sector signature (0xAA55) + boot_signature: u16, +}; + +/// The configuration of the MBR partition scheme after parsing the bytes off the disk. +const MBRConfig = struct { + /// The array of the 4 partitions. + partitions: [4]Partition, +}; + +/// The partition information structure. +pub const Partition = packed struct { + /// If bit 7 (0x80) is set, then it is an active partition and/or bootable + drive_attributes: u8, + + /// The CHS (cylinder, head, sector) start address (u24). + chs_address_start1: u8, + chs_address_start2: u8, + chs_address_start3: u8, + + /// The type of the partition. (There is a long list online for reference.) + partition_type: u8, + + /// The CHS (cylinder, head, sector) end address (u24). + chs_address_end1: u8, + chs_address_end2: u8, + chs_address_end3: u8, + + /// The start LBA (linear block address) of the partition. + lba_start: u32, + + /// The number of sectors of the partition. + number_of_sectors: u32, +}; + +// Check the sizes of the packed structs are packed. +comptime { + std.debug.assert(@sizeOf(MBR) == 512); + std.debug.assert(@sizeOf(Partition) == 16); +} + +/// The sector size of the drive. Unfortunately MBR assumes 512 bytes. +const SECTOR_SIZE: u32 = 512; + +/// +/// A partition stream type based on the MBR stream type. This stream is limited to the bounds of +/// the stream. This stream is a reader(), writer() and seekableStream(). When getting the steam +/// position, the functions will return the relative position from 0 to the length of the stream. +/// +/// Arguments: +/// IN comptime ParentStreamType: type - The type of the parent stream. This will be from the +/// MBR stream. +/// +/// Return: type +/// The partition stream type. +/// +pub fn PartitionStream(comptime ParentStreamType: type) type { + return struct { + + /// The parent stream this the partition is acting on. This would be the MBR stream. + parent_stream: ParentStreamType, + + /// The position in the parent stream in bytes. This is bounded by start_pos and end_pos. + pos: u64, + + /// The start position in bytes of the partition in the parent stream. + start_pos: u64, + + /// The end position in bytes of the partition in the parent stream. + end_pos: u64, + + /// Self reference for this stream. + const Self = @This(); + + /// Standard stream read error. + pub const ReadError = fs_common.GetReadError(ParentStreamType); + + /// Standard stream write error. + pub const WriteError = fs_common.GetWriteError(ParentStreamType); + + /// Standard stream seek error. + pub const SeekError = fs_common.GetSeekError(ParentStreamType) || error{PastEnd}; + + /// Standard stream get stream position error. + pub const GetSeekPosError = fs_common.GetGetSeekPosError(ParentStreamType); + + /// Standard reader type. + pub const Reader = std.io.Reader(*Self, ReadError, read); + + /// Standard writer type. + pub const Writer = std.io.Writer(*Self, WriteError, write); + + /// Standard seekable stream type. + pub const SeekableStream = std.io.SeekableStream( + *Self, + SeekError, + GetSeekPosError, + seekTo, + seekBy, + getPos, + getEndPos, + ); + + /// Standard read function. + fn read(self: *Self, dest: []u8) ReadError!usize { + return self.parent_stream.reader().read(dest); + } + + /// Standard write function. + fn write(self: *Self, bytes: []const u8) WriteError!usize { + return self.parent_stream.writer().write(bytes); + } + + /// Standard seekTo function. + fn seekTo(self: *Self, new_pos: u64) SeekError!void { + const relative_new_pos = new_pos + self.start_pos; + if (relative_new_pos > self.end_pos) { + return SeekError.PastEnd; + } + try self.parent_stream.seekableStream().seekTo(relative_new_pos); + } + + /// Standard seekBy function. + fn seekBy(self: *Self, amount: i64) SeekError!void { + if (amount < 0) { + // Guaranteed to fit as going i64 => u64 + const abs_amount = std.math.absCast(amount); + if (abs_amount > self.start_pos) { + try self.parent_stream.seekableStream().seekBy(0); + self.pos = self.start_pos; + } else { + try self.parent_stream.seekableStream().seekBy(amount); + self.pos -= abs_amount; + } + } else { + if (amount > self.end_pos - self.pos) { + try self.parent_stream.seekableStream().seekBy(@intCast(i64, self.end_pos - self.pos)); + self.pos = self.end_pos; + } else { + try self.parent_stream.seekableStream().seekBy(amount); + self.pos += @intCast(u64, amount); + } + } + } + + /// Standard getEndPos function. + fn getEndPos(self: *Self) GetSeekPosError!u64 { + return self.end_pos - self.start_pos; + } + + /// Standard getPos function. + fn getPos(self: *Self) GetSeekPosError!u64 { + return self.pos - self.start_pos; + } + + /// Standard reader function. + pub fn reader(self: *Self) Reader { + return .{ .context = self }; + } + + /// Standard writer function. + pub fn writer(self: *Self) Writer { + return .{ .context = self }; + } + + /// Standard seekableStream function. + pub fn seekableStream(self: *Self) SeekableStream { + return .{ .context = self }; + } + }; +} + +/// +/// The MBR partition scheme type. +/// +/// Arguments: +/// IN comptime StreamType: type - The underlying stream type the MBR scheme sits on. This can +/// be a disk driver stream or a fixed buffer stream. +/// +/// Return: type +/// The MBR partition type. +/// +pub fn MBRPartition(comptime StreamType: type) type { + return struct { + /// The parsed configuration of the MBR partition scheme. This will be the caches + /// information from the boot sector. + mbr_config: MBRConfig, + + /// The underlying stream + stream: StreamType, + + /// The error set for the MBR partition table. + const Error = error{ + /// If the boot sector doesn't have the 0xAA55 signature as the last word. This would + /// indicate this is not a valid boot sector. + BadMBRMagic, + + /// When reading from the stream, if the read count is less than the expected read, + /// then there is a bad read. + BadRead, + + /// The partition isn't active. + NotActivePartition, + + /// The partition type is unknown. + UnknownPartitionType, + + /// The drive is set to read only. + ReadOnly, + }; + + /// The internal self struct + const MBRSelf = @This(); + + /// + /// Get a bounded stream that represents the partition given. + /// + /// Arguments: + /// IN self: *MBRSelf - The MBR partition structure. + /// IN partition_num: u2 - The partition number to get the stream for. + /// + /// Return: PartitionStream(StreamType) + /// The partition stream. + /// + /// Error: Error + /// Error.NotActivePartition - The partition requested is not active. + /// + pub fn getPartitionStream(self: *MBRSelf, partition_num: u2) Error!PartitionStream(StreamType) { + if (self.mbr_config.partitions[partition_num].drive_attributes & 0x80 != 0x80) { + return Error.NotActivePartition; + } + + const part_info = self.mbr_config.partitions[partition_num]; + if (part_info.partition_type == 0) { + return Error.UnknownPartitionType; + } + + return PartitionStream(StreamType){ + .parent_stream = self.stream, + .pos = part_info.lba_start * SECTOR_SIZE, + .start_pos = part_info.lba_start * SECTOR_SIZE, + .end_pos = (part_info.lba_start + part_info.number_of_sectors) * SECTOR_SIZE, + }; + } + + /// + /// Initialise a MBR partition scheme from a stream that represents a disk. This will + /// will assume the MBR partition boot sector is not set as read only. + /// + /// Arguments: + /// IN allocator: *Allocator - The allocator used for creating a buffer for the boot sector. + /// IN stream: StreamType - The stream to initialise from. This will need to represent a disk. + /// + /// Return: MBRSelf + /// The MBR partition scheme that wraps the stream. + /// + /// Error: Allocator.Error || Error || StreamType.SeekError || StreamType.ReadError + /// Allocator.Error - Error allocating 512 byte buffer for the boot sector. + /// Error - Error parsing the boot sector. + /// StreamType.SeekError - Error reading from the stream. + /// StreamType.ReadError - Error seeking the stream. + /// + pub fn init(allocator: *Allocator, stream: StreamType) (Allocator.Error || Error || fs_common.GetSeekError(StreamType) || fs_common.GetReadError(StreamType))!MBRSelf { + log.debug("Init\n", .{}); + defer log.debug("Done\n", .{}); + + var boot_sector_raw = try allocator.alloc(u8, 512); + defer allocator.free(boot_sector_raw); + + const seek_stream = stream.seekableStream(); + const read_stream = stream.reader(); + + try seek_stream.seekTo(0); + const read_count = try read_stream.readAll(boot_sector_raw[0..]); + if (read_count != 512) { + return Error.BadRead; + } + + // Check the boot signature + if (boot_sector_raw[510] != 0x55 or boot_sector_raw[511] != 0xAA) { + return Error.BadMBRMagic; + } + + const mbr_parsed = std.mem.bytesAsValue(MBR, boot_sector_raw[0..512]); + + // Check if the dick is read only + if (mbr_parsed.read_only == 0x5A5A) { + return Error.ReadOnly; + } + + return MBRSelf{ + .mbr_config = .{ + .partitions = [4]Partition{ mbr_parsed.partition_1, mbr_parsed.partition_2, mbr_parsed.partition_3, mbr_parsed.partition_4 }, + }, + .stream = stream, + }; + } + }; +} + +test "MBRPartition.getPartitionStream - not active drive attribute" { + const allocator = std.testing.allocator; + const stream = &std.io.fixedBufferStream(fs_common.filesystem_bootsector_boot_code[0..]); + var mbr = try MBRPartition(@TypeOf(stream)).init(allocator, stream); + expectError(error.NotActivePartition, mbr.getPartitionStream(0)); + expectError(error.NotActivePartition, mbr.getPartitionStream(1)); + expectError(error.NotActivePartition, mbr.getPartitionStream(2)); + expectError(error.NotActivePartition, mbr.getPartitionStream(3)); +} + +test "MBRPartition.getPartitionStream - no partition type" { + const allocator = std.testing.allocator; + + const size = 1024 * 1024 * 10; + var buffer = try allocator.alloc(u8, size); + defer allocator.free(buffer); + + const stream = &std.io.fixedBufferStream(buffer); + try @import("makefs.zig").MBRPartition.make(.{ .partition_options = .{ 100, null, null, null }, .image_size = size }, stream); + + var mbr = try MBRPartition(@TypeOf(stream)).init(allocator, stream); + + mbr.mbr_config.partitions[0].partition_type = 0x00; + + expectError(error.UnknownPartitionType, mbr.getPartitionStream(0)); +} + +test "MBRPartition.getPartitionStream - success" { + const allocator = std.testing.allocator; + + const size = 1024 * 1024 * 10; + var buffer = try allocator.alloc(u8, size); + defer allocator.free(buffer); + + const stream = &std.io.fixedBufferStream(buffer); + try @import("makefs.zig").MBRPartition.make(.{ .partition_options = .{ 100, null, null, null }, .image_size = size }, stream); + + var mbr = try MBRPartition(@TypeOf(stream)).init(allocator, stream); + const ps = try mbr.getPartitionStream(0); + expectError(error.NotActivePartition, mbr.getPartitionStream(1)); + expectError(error.NotActivePartition, mbr.getPartitionStream(2)); + expectError(error.NotActivePartition, mbr.getPartitionStream(3)); +} + +test "MBRPartition.init - failed alloc" { + const allocator = std.testing.failing_allocator; + const stream = &std.io.fixedBufferStream(&[_]u8{}); + expectError(error.OutOfMemory, MBRPartition(@TypeOf(stream)).init(allocator, stream)); +} + +test "MBRPartition.init - read error" { + const allocator = std.testing.allocator; + const stream = &std.io.fixedBufferStream(&[_]u8{}); + expectError(error.BadRead, MBRPartition(@TypeOf(stream)).init(allocator, stream)); +} + +test "MBRPartition.init - bad boot magic" { + var cp_bootcode = fs_common.filesystem_bootsector_boot_code; + cp_bootcode[510] = 0x00; + cp_bootcode[511] = 0x00; + const allocator = std.testing.allocator; + const stream = &std.io.fixedBufferStream(cp_bootcode[0..]); + expectError(error.BadMBRMagic, MBRPartition(@TypeOf(stream)).init(allocator, stream)); +} + +test "MBRPartition.init - read only disk" { + var cp_bootcode = fs_common.filesystem_bootsector_boot_code; + cp_bootcode[444] = 0x5A; + cp_bootcode[445] = 0x5A; + const allocator = std.testing.allocator; + const stream = &std.io.fixedBufferStream(cp_bootcode[0..]); + expectError(error.ReadOnly, MBRPartition(@TypeOf(stream)).init(allocator, stream)); +} + +test "MBRPartition.init - success" { + const allocator = std.testing.allocator; + const stream = &std.io.fixedBufferStream(fs_common.filesystem_bootsector_boot_code[0..]); + const mbr = try MBRPartition(@TypeOf(stream)).init(allocator, stream); +} diff --git a/src/kernel/kmain_64.zig b/src/kernel/kmain_64.zig index 0b0cf8b6..f457442d 100644 --- a/src/kernel/kmain_64.zig +++ b/src/kernel/kmain_64.zig @@ -79,4 +79,8 @@ export fn kmain(boot_payload: *const arch.BootPayload) void { test "" { std.testing.refAllDecls(@This()); + + // Force testing file system stuff + _ = @import("filesystem/fat32.zig"); + _ = @import("filesystem/mbr.zig"); } diff --git a/src/kernel/time.zig b/src/kernel/time.zig new file mode 100644 index 00000000..e737f86c --- /dev/null +++ b/src/kernel/time.zig @@ -0,0 +1,31 @@ +const builtin = @import("builtin"); + +pub const DateTime = struct { + second: u32, + minute: u32, + hour: u32, + day: u32, + month: u32, + year: u32, + century: u32, + day_of_week: u32, +}; + +pub fn getDateTime() DateTime { + return switch (builtin.os.tag) { + .freestanding => @import("../arch.zig").internals.getDateTime(), + // TODO: Use the std lib std.time.timestamp() and convert + // Hard code 12:12:13 12/12/12 for testing + // https://github.com/ziglang/zig/issues/8396 + else => .{ + .second = 13, + .minute = 12, + .hour = 12, + .day = 12, + .month = 12, + .year = 2012, + .century = 2000, + .day_of_week = 4, + }, + }; +} diff --git a/test/mock/kernel/arch_mock.zig b/test/mock/kernel/arch_mock.zig index 42cba269..3af3285b 100644 --- a/test/mock/kernel/arch_mock.zig +++ b/test/mock/kernel/arch_mock.zig @@ -68,6 +68,41 @@ pub const END_VIRTUAL_MEMORY: usize = switch (builtin.arch) { else => unreachable, }; +pub const filesystem_bootsector_boot_code = [512]u8{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, +}; + // The virtual/physical start/end of the kernel code var KERNEL_PHYSADDR_START: u32 = 0x00100000; var KERNEL_PHYSADDR_END: u32 = 0x01000000;