diff --git a/.gitignore b/.gitignore index e65d384..664b769 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ *.i*86 *.x86_64 *.hex +*.initrd # Debug files *.dSYM/ diff --git a/build.zig b/build.zig index 2ebb6c4..13bf199 100644 --- a/build.zig +++ b/build.zig @@ -2,10 +2,13 @@ const std = @import("std"); const builtin = @import("builtin"); const rt = @import("test/runtime_test.zig"); const RuntimeStep = rt.RuntimeStep; +const Allocator = std.mem.Allocator; const Builder = std.build.Builder; +const Step = std.build.Step; const Target = std.Target; const CrossTarget = std.zig.CrossTarget; const fs = std.fs; +const File = fs.File; const Mode = builtin.Mode; const TestMode = rt.TestMode; const ArrayList = std.ArrayList; @@ -38,6 +41,7 @@ pub fn build(b: *Builder) !void { const iso_dir_path = try fs.path.join(b.allocator, &[_][]const u8{ b.exe_dir, "iso" }); 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 build_mode = b.standardReleaseOptions(); comptime var test_mode_desc: []const u8 = "\n "; @@ -59,11 +63,23 @@ pub fn build(b: *Builder) !void { exec.setTarget(target); const make_iso = switch (target.getCpuArch()) { - .i386 => b.addSystemCommand(&[_][]const u8{ "./makeiso.sh", boot_path, modules_path, iso_dir_path, exec.getOutputPath(), output_iso }), + .i386 => b.addSystemCommand(&[_][]const u8{ "./makeiso.sh", boot_path, modules_path, iso_dir_path, exec.getOutputPath(), ramdisk_path, output_iso }), else => unreachable, }; make_iso.step.dependOn(&exec.step); + var ramdisk_files_al = ArrayList([]const u8).init(b.allocator); + defer ramdisk_files_al.deinit(); + + // Add some test files for the ramdisk runtime tests + if (test_mode == .Initialisation) { + try ramdisk_files_al.append("test/ramdisk_test1.txt"); + try ramdisk_files_al.append("test/ramdisk_test2.txt"); + } + + 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); const test_step = b.step("test", "Run tests"); @@ -149,3 +165,120 @@ pub fn build(b: *Builder) !void { }); debug_step.dependOn(&debug_cmd.step); } + +/// The ramdisk make step for creating the initial ramdisk. +const RamdiskStep = struct { + /// The Step, that is all you need to know + step: Step, + + /// The builder pointer, also all you need to know + builder: *Builder, + + /// The target for the build + target: CrossTarget, + + /// The list of files to be added to the ramdisk + files: []const []const u8, + + /// The path to where the ramdisk will be written to. + out_file_path: []const u8, + + /// The possible errors for creating a ramdisk + const Error = (error{EndOfStream} || File.ReadError || File.GetPosError || Allocator.Error || File.WriteError || File.OpenError); + + /// + /// Create and write the files to a raw ramdisk in the format: + /// (NumOfFiles:usize)[(name_length:usize)(name:u8[name_length])(content_length:usize)(content:u8[content_length])]* + /// + /// Argument: + /// IN comptime Usize: type - The usize type for the architecture. + /// IN self: *RamdiskStep - Self. + /// + /// Error: Error + /// Errors for opening, reading and writing to and from files and for allocating memory. + /// + fn writeRamdisk(comptime Usize: type, self: *RamdiskStep) Error!void { + // 1MB, don't think the ram disk should be very big + const max_file_size = 1024 * 1024 * 1024; + + // Open the out file + var ramdisk = try fs.cwd().createFile(self.out_file_path, .{}); + defer ramdisk.close(); + + // Get the targets endian + const endian = self.target.getCpuArch().endian(); + + // First write the number of files/headers + std.debug.assert(self.files.len < std.math.maxInt(Usize)); + try ramdisk.writer().writeInt(Usize, @truncate(Usize, self.files.len), endian); + var current_offset: usize = 0; + for (self.files) |file_path| { + // Open, and read the file. Can get the size from this as well + const file_content = try fs.cwd().readFileAlloc(self.builder.allocator, file_path, max_file_size); + + // Get the last occurrence of / for the file name, if there isn't one, then the file_path is the name + const file_name_index = if (std.mem.lastIndexOf(u8, file_path, "/")) |index| index + 1 else 0; + + // Write the header and file content to the ramdisk + // Name length + std.debug.assert(file_path[file_name_index..].len < std.math.maxInt(Usize)); + try ramdisk.writer().writeInt(Usize, @truncate(Usize, file_path[file_name_index..].len), endian); + + // Name + try ramdisk.writer().writeAll(file_path[file_name_index..]); + + // Length + std.debug.assert(file_content.len < std.math.maxInt(Usize)); + try ramdisk.writer().writeInt(Usize, @truncate(Usize, file_content.len), endian); + + // File contest + try ramdisk.writer().writeAll(file_content); + + // Increment the offset to the new location + current_offset += @sizeOf(Usize) * 3 + file_path[file_name_index..].len + file_content.len; + } + } + + /// + /// The make function that is called by the builder. This will create the qemu process with the + /// stdout as a Pipe. Then create the read thread to read the logs from the qemu stdout. Then + /// will call the test function to test a specifics part of the OS defined by the test mode. + /// + /// Arguments: + /// IN step: *Step - The step of this step. + /// + /// Error: Error + /// Errors for opening, reading and writing to and from files and for allocating memory. + /// + fn make(step: *Step) Error!void { + const self = @fieldParentPtr(RamdiskStep, "step", step); + switch (self.target.getCpuArch()) { + .i386 => try writeRamdisk(u32, self), + else => unreachable, + } + } + + /// + /// Create a ramdisk step. + /// + /// Argument: + /// IN builder: *Builder - The build builder. + /// IN target: CrossTarget - The target for the build. + /// IN files: []const []const u8 - The file names to be added to the ramdisk. + /// IN out_file_path: []const u8 - The output file path. + /// + /// Return: *RamdiskStep + /// The ramdisk step pointer to add to the build process. + /// + pub fn create(builder: *Builder, target: CrossTarget, files: []const []const u8, out_file_path: []const u8) *RamdiskStep { + const ramdisk_step = builder.allocator.create(RamdiskStep) catch unreachable; + ramdisk_step.* = .{ + .step = Step.init(.Custom, builder.fmt("Ramdisk", .{}), builder.allocator, make), + .builder = builder, + .target = target, + .files = files, + .out_file_path = out_file_path, + }; + return ramdisk_step; + } +}; diff --git a/grub/grub.cfg b/grub/grub.cfg index 2474c0f..2c5352c 100644 --- a/grub/grub.cfg +++ b/grub/grub.cfg @@ -4,5 +4,6 @@ set default=0 menuentry "pluto" { multiboot /boot/pluto.elf module /modules/kernel.map kernel.map + module /modules/initrd.ramdisk initrd.ramdisk boot } diff --git a/makeiso.sh b/makeiso.sh index c137f74..b316bd9 100755 --- a/makeiso.sh +++ b/makeiso.sh @@ -4,7 +4,8 @@ BOOT_DIR=$1 MODULES_DIR=$2 ISO_DIR=$3 PLUTO_ELF=$4 -OUTPUT_FILE=$5 +RAMDISK=$5 +OUTPUT_FILE=$6 MAP_FILE=$MODULES_DIR/'kernel.map' @@ -23,6 +24,7 @@ mkdir -p $MODULES_DIR cp -r grub $BOOT_DIR cp $PLUTO_ELF $BOOT_DIR/"pluto.elf" +cp $RAMDISK $MODULES_DIR/"initrd.ramdisk" # 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 diff --git a/src/kernel/arch/x86/paging.zig b/src/kernel/arch/x86/paging.zig index a8306c6..7db4531 100644 --- a/src/kernel/arch/x86/paging.zig +++ b/src/kernel/arch/x86/paging.zig @@ -14,6 +14,7 @@ const tty = @import("../../tty.zig"); const mem = @import("../../mem.zig"); const vmm = @import("../../vmm.zig"); const multiboot = @import("multiboot.zig"); +const Allocator = std.mem.Allocator; /// An array of directory entries and page tables. Forms the first level of paging and covers the entire 4GB memory space. pub const Directory = packed struct { @@ -169,15 +170,15 @@ inline fn clearAttribute(val: *align(1) u32, attr: u32) void { /// IN allocator: *Allocator - The allocator to use to map any tables needed /// OUT dir: *Directory - The directory that this entry is in /// -/// Error: vmm.MapperError || std.mem.Allocator.Error +/// Error: vmm.MapperError || Allocator.Error /// vmm.MapperError.InvalidPhysicalAddress - The physical start address is greater than the end /// vmm.MapperError.InvalidVirtualAddress - The virtual start address is greater than the end or is larger than 4GB /// vmm.MapperError.AddressMismatch - The differences between the virtual addresses and the physical addresses aren't the same /// vmm.MapperError.MisalignedPhysicalAddress - One or both of the physical addresses aren't page size aligned /// vmm.MapperError.MisalignedVirtualAddress - One or both of the virtual addresses aren't page size aligned -/// std.mem.Allocator.Error.* - See std.mem.Allocator.alignedAlloc +/// Allocator.Error.* - See Allocator.alignedAlloc /// -fn mapDirEntry(dir: *Directory, virt_start: usize, virt_end: usize, phys_start: usize, phys_end: usize, attrs: vmm.Attributes, allocator: *std.mem.Allocator) (vmm.MapperError || std.mem.Allocator.Error)!void { +fn mapDirEntry(dir: *Directory, virt_start: usize, virt_end: usize, phys_start: usize, phys_end: usize, attrs: vmm.Attributes, allocator: *Allocator) (vmm.MapperError || Allocator.Error)!void { if (phys_start > phys_end) { return vmm.MapperError.InvalidPhysicalAddress; } @@ -301,13 +302,13 @@ fn mapTableEntry(entry: *align(1) TableEntry, phys_addr: usize, attrs: vmm.Attri /// IN physical_start: usize - The start of the physical region to map to /// IN physical_end: usize - The end (exclusive) of the physical region to map to /// IN attrs: vmm.Attributes - The attributes to apply to this mapping -/// IN/OUT allocator: *std.mem.Allocator - The allocator to use to allocate any intermediate data structures required to map this region +/// IN/OUT allocator: *Allocator - The allocator to use to allocate any intermediate data structures required to map this region /// IN/OUT dir: *Directory - The page directory to map within /// -/// Error: vmm.MapperError || std.mem.Allocator.Error +/// Error: vmm.MapperError || Allocator.Error /// * - See mapDirEntry /// -pub fn map(virt_start: usize, virt_end: usize, phys_start: usize, phys_end: usize, attrs: vmm.Attributes, allocator: *std.mem.Allocator, dir: *Directory) (std.mem.Allocator.Error || vmm.MapperError)!void { +pub fn map(virt_start: usize, virt_end: usize, phys_start: usize, phys_end: usize, attrs: vmm.Attributes, allocator: *Allocator, dir: *Directory) (Allocator.Error || vmm.MapperError)!void { var virt_addr = virt_start; var phys_addr = phys_start; var page = virt_addr / PAGE_SIZE_4KB; @@ -329,10 +330,10 @@ pub fn map(virt_start: usize, virt_end: usize, phys_start: usize, phys_end: usiz /// IN virtual_end: usize - The end (exclusive) of the virtual region to unmap /// IN/OUT dir: *Directory - The page directory to unmap within /// -/// Error: std.mem.Allocator.Error || vmm.MapperError +/// Error: Allocator.Error || vmm.MapperError /// vmm.MapperError.NotMapped - If the region being unmapped wasn't mapped in the first place /// -pub fn unmap(virtual_start: usize, virtual_end: usize, dir: *Directory) (std.mem.Allocator.Error || vmm.MapperError)!void { +pub fn unmap(virtual_start: usize, virtual_end: usize, dir: *Directory) (Allocator.Error || vmm.MapperError)!void { var virt_addr = virtual_start; var page = virt_addr / PAGE_SIZE_4KB; var entry_idx = virt_addr / PAGE_SIZE_4MB; diff --git a/src/kernel/initrd.zig b/src/kernel/initrd.zig new file mode 100644 index 0000000..1cee341 --- /dev/null +++ b/src/kernel/initrd.zig @@ -0,0 +1,675 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const is_test = builtin.is_test; +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; +const expectError = std.testing.expectError; +const expectEqualSlices = std.testing.expectEqualSlices; +const build_options = @import("build_options"); +const mock_path = build_options.mock_path; +const Allocator = std.mem.Allocator; +const AutoHashMap = std.AutoHashMap; +const vfs = @import("vfs.zig"); +const mem = if (is_test) @import(mock_path ++ "mem_mock.zig") else @import("mem.zig"); +const panic = if (is_test) @import(mock_path ++ "panic_mock.zig").panic else @import("panic.zig").panic; + +/// The Initrd file system struct. +/// Format of raw ramdisk: +/// (NumOfFiles:usize)[(name_length:usize)(name:u8[name_length])(content_length:usize)(content:u8[content_length])]* +pub const InitrdFS = struct { + /// The ramdisk header that stores pointers to the raw ramdisk for the name and file content. + /// As the ramdisk is read only, these can be const pointers. + const InitrdHeader = struct { + /// The name of the file + name: []const u8, + + /// The content of the file + content: []const u8, + }; + + /// The error set for the ramdisk file system. + const Error = error{ + /// The error for an invalid raw ramdisk when + /// parsing. + InvalidRamDisk, + }; + + const Self = @This(); + + /// A mapping of opened files so can easily retrieved opened files for reading. + opened_files: AutoHashMap(*const vfs.Node, *InitrdHeader), + + /// The underlying file system + fs: *vfs.FileSystem, + + /// The allocator used for allocating memory for opening and reading. + allocator: *Allocator, + + /// The list of files in the ram disk. These will be pointers into the raw ramdisk to save on + /// allocations. + files: []InitrdHeader, + + /// The root node for the ramdisk file system. This is just a root directory as there is not + /// subdirectories. + root_node: *vfs.Node, + + /// See vfs.FileSystem.instance + instance: usize, + + /// See vfs.FileSystem.getRootNode + fn getRootNode(fs: *const vfs.FileSystem) *const vfs.DirNode { + var self = @fieldParentPtr(InitrdFS, "instance", fs.instance); + return &self.root_node.Dir; + } + + /// See vfs.FileSystem.close + fn close(fs: *const vfs.FileSystem, node: *const vfs.FileNode) void { + var self = @fieldParentPtr(InitrdFS, "instance", fs.instance); + const cast_node = @ptrCast(*const vfs.Node, node); + // As close can't error, if provided with a invalid Node that isn't opened or try to close + // the same file twice, will just do nothing. + if (self.opened_files.contains(cast_node)) { + _ = self.opened_files.remove(cast_node); + self.allocator.destroy(node); + } + } + + /// See vfs.FileSystem.read + fn read(fs: *const vfs.FileSystem, node: *const vfs.FileNode, len: usize) (Allocator.Error || vfs.Error)![]u8 { + var self = @fieldParentPtr(InitrdFS, "instance", fs.instance); + const cast_node = @ptrCast(*const vfs.Node, node); + const file_header = self.opened_files.get(cast_node) orelse return vfs.Error.NotOpened; + const length = std.math.min(len, file_header.content.len); + const buff = try self.allocator.alloc(u8, length); + std.mem.copy(u8, buff, file_header.content[0..length]); + return buff; + } + + /// See vfs.FileSystem.write + fn write(fs: *const vfs.FileSystem, node: *const vfs.FileNode, bytes: []const u8) (Allocator.Error || vfs.Error)!void {} + + /// See vfs.FileSystem.open + fn open(fs: *const vfs.FileSystem, dir: *const vfs.DirNode, name: []const u8, flags: vfs.OpenFlags) (Allocator.Error || vfs.Error)!*vfs.Node { + var self = @fieldParentPtr(InitrdFS, "instance", fs.instance); + switch (flags) { + .CREATE_DIR, .CREATE_FILE => return vfs.Error.InvalidFlags, + .NO_CREATION => { + for (self.files) |*file| { + if (std.mem.eql(u8, file.name, name)) { + // Opening 2 files of the same name, will create 2 different Nodes + // Create a node + var file_node = try self.allocator.create(vfs.Node); + file_node.* = .{ .File = .{ .fs = self.fs } }; + try self.opened_files.put(file_node, file); + return file_node; + } + } + return vfs.Error.NoSuchFileOrDir; + }, + } + } + + /// + /// Free all memory allocated. + /// + /// Arguments: + /// IN self: *Self - Self + /// + pub fn deinit(self: *Self) void { + // If there are any files open, then we have a error. + std.debug.assert(self.opened_files.items().len == 0); + self.allocator.destroy(self.root_node); + self.allocator.destroy(self.fs); + self.opened_files.deinit(); + self.allocator.free(self.files); + self.allocator.destroy(self); + } + + /// + /// Initialise a ramdisk file system from a raw ramdisk in memory provided by the bootloader. + /// Any memory allocated will be freed. + /// + /// Arguments: + /// IN rd_module: mem.Module - The bootloader module that contains the raw ramdisk. + /// IN allocator: *Allocator - The allocator used for initialising any memory needed. + /// + /// Return: *InitrdFS + /// A pointer to the ram disk file system. + /// + /// Error: Allocator.Error || Error + /// error.OutOfMemory - If there isn't enough memory for initialisation. Any memory + /// allocated will be freed. + /// error.InvalidRamDisk - If the provided raw ramdisk is invalid. This can be due to a + /// mis-match of the number of files to the length of the raw + /// ramdisk or the wrong length provided to cause undefined parsed + /// lengths for other parts of the ramdisk. + /// + pub fn init(rd_module: mem.Module, allocator: *Allocator) (Allocator.Error || Error)!*InitrdFS { + std.log.info(.initrd, "Init\n", .{}); + defer std.log.info(.initrd, "Done\n", .{}); + + const rd_len: usize = rd_module.region.end - rd_module.region.start; + const ramdisk_bytes = @intToPtr([*]u8, rd_module.region.start)[0..rd_len]; + // First @sizeOf(usize) bytes is the number of files + const num_of_files = std.mem.readIntSlice(usize, ramdisk_bytes[0..], builtin.endian); + var headers = try allocator.alloc(InitrdHeader, num_of_files); + errdefer allocator.free(headers); + + // Populate the headers + var i: usize = 0; + // Keep track of the offset into the ramdisk memory + var current_offset: usize = @sizeOf(usize); + while (i < num_of_files) : (i += 1) { + // We don't need to store the name length any more as we have the name.len + const name_len = std.mem.readIntSlice(usize, ramdisk_bytes[current_offset..], builtin.endian); + current_offset += @sizeOf(usize); + if (current_offset >= rd_len) { + return Error.InvalidRamDisk; + } + headers[i].name = ramdisk_bytes[current_offset .. current_offset + name_len]; + current_offset += name_len; + if (current_offset >= rd_len) { + return Error.InvalidRamDisk; + } + const content_len = std.mem.readIntSlice(usize, ramdisk_bytes[current_offset..], builtin.endian); + current_offset += @sizeOf(usize); + if (current_offset >= rd_len) { + return Error.InvalidRamDisk; + } + headers[i].content = ramdisk_bytes[current_offset .. current_offset + content_len]; + current_offset += content_len; + // We could be at the end of the ramdisk, so only need to check for grater than. + if (current_offset > rd_len) { + return Error.InvalidRamDisk; + } + } + + // Make sure we are at the end + if (current_offset != rd_len) { + return Error.InvalidRamDisk; + } + + var rd_fs = try allocator.create(InitrdFS); + errdefer allocator.destroy(rd_fs); + var fs = try allocator.create(vfs.FileSystem); + errdefer allocator.destroy(fs); + var root_node = try allocator.create(vfs.Node); + + root_node.* = .{ .Dir = .{ .fs = fs, .mount = null } }; + fs.* = .{ .open = open, .close = close, .read = read, .write = write, .instance = &rd_fs.instance, .getRootNode = getRootNode }; + + rd_fs.* = .{ + .opened_files = AutoHashMap(*const vfs.Node, *InitrdHeader).init(allocator), + .fs = fs, + .allocator = allocator, + .files = headers, + .root_node = root_node, + .instance = 1, + }; + + switch (build_options.test_mode) { + .Initialisation => runtimeTests(rd_fs), + else => {}, + } + + return rd_fs; + } +}; + +/// +/// Crate a raw ramdisk in memory to be used to initialise the ramdisk file system. This create +/// three files: test1.txt, test2.txt and test3.txt. +/// +/// Arguments: +/// IN allocator: *Allocator - The allocator to alloc the raw ramdisk. +/// +/// Return: []u8 +/// The bytes of the raw ramdisk in memory. +/// +/// Error: Allocator.Error +/// error.OutOfMemory - If there isn't enough memory for the in memory ramdisk. +/// +fn createInitrd(allocator: *Allocator) Allocator.Error![]u8 { + // Create 3 valid ramdisk files in memory + const file_names = [_][]const u8{ "test1.txt", "test2.txt", "test3.txt" }; + const file_contents = [_][]const u8{ "This is a test", "This is a test: part 2", "This is a test: the prequel" }; + + var sum: usize = 0; + const files_length = for ([_]usize{ 0, 1, 2 }) |i| { + sum += @sizeOf(usize) + file_names[i].len + @sizeOf(usize) + file_contents[i].len; + } else sum; + + const total_ramdisk_len = @sizeOf(usize) + files_length; + var ramdisk_mem = try allocator.alloc(u8, total_ramdisk_len); + + // Copy the data into the allocated memory + std.mem.writeIntSlice(usize, ramdisk_mem[0..], 3, builtin.endian); + var current_offset: usize = @sizeOf(usize); + inline for ([_]usize{ 0, 1, 2 }) |i| { + // Name len + std.mem.writeIntSlice(usize, ramdisk_mem[current_offset..], file_names[i].len, builtin.endian); + current_offset += @sizeOf(usize); + // Name + std.mem.copy(u8, ramdisk_mem[current_offset..], file_names[i]); + current_offset += file_names[i].len; + // File len + std.mem.writeIntSlice(usize, ramdisk_mem[current_offset..], file_contents[i].len, builtin.endian); + current_offset += @sizeOf(usize); + // File content + std.mem.copy(u8, ramdisk_mem[current_offset..], file_contents[i]); + current_offset += file_contents[i].len; + } + // Make sure we are full + expectEqual(current_offset, total_ramdisk_len); + return ramdisk_mem; +} + +test "init with files valid" { + var ramdisk_mem = try createInitrd(std.testing.allocator); + defer std.testing.allocator.free(ramdisk_mem); + + const ramdisk_range = .{ .start = @ptrToInt(ramdisk_mem.ptr), .end = @ptrToInt(ramdisk_mem.ptr) + ramdisk_mem.len }; + const ramdisk_module = .{ .region = ramdisk_range, .name = "ramdisk.initrd" }; + var fs = try InitrdFS.init(ramdisk_module, std.testing.allocator); + defer fs.deinit(); + + expectEqual(fs.files.len, 3); + expectEqualSlices(u8, fs.files[0].name, "test1.txt"); + expectEqualSlices(u8, fs.files[1].content, "This is a test: part 2"); + expectEqual(fs.opened_files.items().len, 0); +} + +test "init with files invalid - invalid number of files" { + var ramdisk_mem = try createInitrd(std.testing.allocator); + // Override the number of files + std.mem.writeIntSlice(usize, ramdisk_mem[0..], 10, builtin.endian); + defer std.testing.allocator.free(ramdisk_mem); + + const ramdisk_range = .{ .start = @ptrToInt(ramdisk_mem.ptr), .end = @ptrToInt(ramdisk_mem.ptr) + ramdisk_mem.len }; + const ramdisk_module = .{ .region = ramdisk_range, .name = "ramdisk.initrd" }; + expectError(error.InvalidRamDisk, InitrdFS.init(ramdisk_module, std.testing.allocator)); + + // Override the number of files + std.mem.writeIntSlice(usize, ramdisk_mem[0..], 0, builtin.endian); + expectError(error.InvalidRamDisk, InitrdFS.init(ramdisk_module, std.testing.allocator)); +} + +test "init with files invalid - mix - bad" { + // TODO: Craft a ramdisk that would parse but is invalid + // This is possible, but will think about this another time + // Challenge, make this a effective security vulnerability + // P.S. I don't know if adding magics will stop this + { + var ramdisk_mem = try createInitrd(std.testing.allocator); + // Override the first file name length, make is shorter + std.mem.writeIntSlice(usize, ramdisk_mem[4..], 2, builtin.endian); + defer std.testing.allocator.free(ramdisk_mem); + + const ramdisk_range = .{ .start = @ptrToInt(ramdisk_mem.ptr), .end = @ptrToInt(ramdisk_mem.ptr) + ramdisk_mem.len }; + const ramdisk_module = .{ .region = ramdisk_range, .name = "ramdisk.initrd" }; + expectError(error.InvalidRamDisk, InitrdFS.init(ramdisk_module, std.testing.allocator)); + } + + { + var ramdisk_mem = try createInitrd(std.testing.allocator); + // Override the first file name length, make is 4 shorter + std.mem.writeIntSlice(usize, ramdisk_mem[4..], 5, builtin.endian); + // Override the second file name length, make is 4 longer + std.mem.writeIntSlice(usize, ramdisk_mem[35..], 13, builtin.endian); + defer std.testing.allocator.free(ramdisk_mem); + + const ramdisk_range = .{ .start = @ptrToInt(ramdisk_mem.ptr), .end = @ptrToInt(ramdisk_mem.ptr) + ramdisk_mem.len }; + const ramdisk_module = .{ .region = ramdisk_range, .name = "ramdisk.initrd" }; + expectError(error.InvalidRamDisk, InitrdFS.init(ramdisk_module, std.testing.allocator)); + } +} + +test "init with files cleans memory if OutOfMemory" { + // There are 4 allocations + for ([_]usize{ 0, 1, 2, 3 }) |i| { + { + var fa = std.testing.FailingAllocator.init(std.testing.allocator, i); + + var ramdisk_mem = try createInitrd(std.testing.allocator); + defer std.testing.allocator.free(ramdisk_mem); + + const ramdisk_range = .{ .start = @ptrToInt(ramdisk_mem.ptr), .end = @ptrToInt(ramdisk_mem.ptr) + ramdisk_mem.len }; + const ramdisk_module = .{ .region = ramdisk_range, .name = "ramdisk.initrd" }; + expectError(error.OutOfMemory, InitrdFS.init(ramdisk_module, &fa.allocator)); + } + + // Ensure we have freed any memory allocated + try std.testing.allocator_instance.validate(); + } +} + +test "getRootNode" { + var ramdisk_mem = try createInitrd(std.testing.allocator); + defer std.testing.allocator.free(ramdisk_mem); + + const ramdisk_range = .{ .start = @ptrToInt(ramdisk_mem.ptr), .end = @ptrToInt(ramdisk_mem.ptr) + ramdisk_mem.len }; + const ramdisk_module = .{ .region = ramdisk_range, .name = "ramdisk.initrd" }; + var fs = try InitrdFS.init(ramdisk_module, std.testing.allocator); + defer fs.deinit(); + + expectEqual(fs.fs.getRootNode(fs.fs), &fs.root_node.Dir); +} + +test "open valid file" { + var ramdisk_mem = try createInitrd(std.testing.allocator); + defer std.testing.allocator.free(ramdisk_mem); + + const ramdisk_range = .{ .start = @ptrToInt(ramdisk_mem.ptr), .end = @ptrToInt(ramdisk_mem.ptr) + ramdisk_mem.len }; + const ramdisk_module = .{ .region = ramdisk_range, .name = "ramdisk.initrd" }; + var fs = try InitrdFS.init(ramdisk_module, std.testing.allocator); + defer fs.deinit(); + + vfs.setRoot(fs.root_node); + + var file1 = try vfs.openFile("/test1.txt", .NO_CREATION); + defer file1.close(); + + var file1_node = @ptrCast(*const vfs.Node, file1); + + expectEqual(fs.opened_files.items().len, 1); + expectEqualSlices(u8, fs.opened_files.get(file1_node).?.name, "test1.txt"); + + var file3_node = try vfs.open("/test3.txt", .NO_CREATION); + defer file3_node.File.close(); + + expectEqual(fs.opened_files.items().len, 2); + expectEqualSlices(u8, fs.opened_files.get(file3_node).?.content, "This is a test: the prequel"); + + var dir1 = try vfs.openDir("/", .NO_CREATION); + expectEqual(&fs.root_node.Dir, dir1); + var file2 = &(try dir1.open("test2.txt", .NO_CREATION)).File; + defer file2.close(); + + expectEqual(fs.opened_files.items().len, 3); +} + +test "open fail with invalid flags" { + var ramdisk_mem = try createInitrd(std.testing.allocator); + defer std.testing.allocator.free(ramdisk_mem); + + const ramdisk_range = .{ .start = @ptrToInt(ramdisk_mem.ptr), .end = @ptrToInt(ramdisk_mem.ptr) + ramdisk_mem.len }; + const ramdisk_module = .{ .region = ramdisk_range, .name = "ramdisk.initrd" }; + var fs = try InitrdFS.init(ramdisk_module, std.testing.allocator); + defer fs.deinit(); + + vfs.setRoot(fs.root_node); + + expectError(error.InvalidFlags, vfs.openFile("/text10.txt", .CREATE_DIR)); + expectError(error.InvalidFlags, vfs.openFile("/text10.txt", .CREATE_FILE)); + expectError(error.InvalidFlags, vfs.openDir("/text10.txt", .CREATE_DIR)); + expectError(error.InvalidFlags, vfs.openDir("/text10.txt", .CREATE_FILE)); + expectError(error.InvalidFlags, vfs.openFile("/test/", .CREATE_DIR)); + expectError(error.InvalidFlags, vfs.openFile("/test/", .CREATE_FILE)); + expectError(error.InvalidFlags, vfs.openDir("/test/", .CREATE_DIR)); + expectError(error.InvalidFlags, vfs.openDir("/test/", .CREATE_FILE)); +} + +test "open fail with NoSuchFileOrDir" { + expectError(error.NoSuchFileOrDir, vfs.openFile("/text10.txt", .NO_CREATION)); + expectError(error.NoSuchFileOrDir, vfs.openDir("/temp/", .NO_CREATION)); +} + +test "open a file, out of memory" { + var fa = std.testing.FailingAllocator.init(std.testing.allocator, 4); + + var ramdisk_mem = try createInitrd(std.testing.allocator); + defer std.testing.allocator.free(ramdisk_mem); + + const ramdisk_range = .{ .start = @ptrToInt(ramdisk_mem.ptr), .end = @ptrToInt(ramdisk_mem.ptr) + ramdisk_mem.len }; + const ramdisk_module = .{ .region = ramdisk_range, .name = "ramdisk.initrd" }; + var fs = try InitrdFS.init(ramdisk_module, &fa.allocator); + defer fs.deinit(); + + vfs.setRoot(fs.root_node); + + expectError(error.OutOfMemory, vfs.openFile("/test1.txt", .NO_CREATION)); +} + +test "open two of the same file" { + var ramdisk_mem = try createInitrd(std.testing.allocator); + defer std.testing.allocator.free(ramdisk_mem); + + const ramdisk_range = .{ .start = @ptrToInt(ramdisk_mem.ptr), .end = @ptrToInt(ramdisk_mem.ptr) + ramdisk_mem.len }; + const ramdisk_module = .{ .region = ramdisk_range, .name = "ramdisk.initrd" }; + var fs = try InitrdFS.init(ramdisk_module, std.testing.allocator); + defer fs.deinit(); + + vfs.setRoot(fs.root_node); + + const file1 = try vfs.openFile("/test1.txt", .NO_CREATION); + defer file1.close(); + + const file2 = try vfs.openFile("/test1.txt", .NO_CREATION); + defer file2.close(); + + expectEqual(fs.opened_files.items().len, 2); + expect(file1 != file2); + + const b1 = try file1.read(128); + defer std.testing.allocator.free(b1); + const b2 = try file2.read(128); + defer std.testing.allocator.free(b2); + expectEqualSlices(u8, b1, b2); +} + +test "close a file" { + var ramdisk_mem = try createInitrd(std.testing.allocator); + defer std.testing.allocator.free(ramdisk_mem); + + const ramdisk_range = .{ .start = @ptrToInt(ramdisk_mem.ptr), .end = @ptrToInt(ramdisk_mem.ptr) + ramdisk_mem.len }; + const ramdisk_module = .{ .region = ramdisk_range, .name = "ramdisk.initrd" }; + var fs = try InitrdFS.init(ramdisk_module, std.testing.allocator); + defer fs.deinit(); + + vfs.setRoot(fs.root_node); + + var file1 = try vfs.openFile("/test1.txt", .NO_CREATION); + + var file1_node = @ptrCast(*const vfs.Node, file1); + + expectEqual(fs.opened_files.items().len, 1); + + var file3_node = try vfs.open("/test3.txt", .NO_CREATION); + + expectEqual(fs.opened_files.items().len, 2); + file1.close(); + expectEqual(fs.opened_files.items().len, 1); + + var dir1 = try vfs.openDir("/", .NO_CREATION); + expectEqual(&fs.root_node.Dir, dir1); + var file2 = &(try dir1.open("test2.txt", .NO_CREATION)).File; + defer file2.close(); + + expectEqual(fs.opened_files.items().len, 2); + file3_node.File.close(); + expectEqual(fs.opened_files.items().len, 1); +} + +test "close a non-opened file" { + var ramdisk_mem = try createInitrd(std.testing.allocator); + defer std.testing.allocator.free(ramdisk_mem); + + const ramdisk_range = .{ .start = @ptrToInt(ramdisk_mem.ptr), .end = @ptrToInt(ramdisk_mem.ptr) + ramdisk_mem.len }; + const ramdisk_module = .{ .region = ramdisk_range, .name = "ramdisk.initrd" }; + var fs = try InitrdFS.init(ramdisk_module, std.testing.allocator); + defer fs.deinit(); + + vfs.setRoot(fs.root_node); + + // Open a valid file + var file1 = try vfs.openFile("/test1.txt", .NO_CREATION); + defer file1.close(); + + // Only one file open + expectEqual(fs.opened_files.items().len, 1); + + // Craft a Node + var fake_node = try std.testing.allocator.create(vfs.Node); + defer std.testing.allocator.destroy(fake_node); + fake_node.* = .{ .File = .{ .fs = fs.fs } }; + fake_node.File.close(); + + // Still only one file open + expectEqual(fs.opened_files.items().len, 1); +} + +test "read a file" { + var ramdisk_mem = try createInitrd(std.testing.allocator); + defer std.testing.allocator.free(ramdisk_mem); + + const ramdisk_range = .{ .start = @ptrToInt(ramdisk_mem.ptr), .end = @ptrToInt(ramdisk_mem.ptr) + ramdisk_mem.len }; + const ramdisk_module = .{ .region = ramdisk_range, .name = "ramdisk.initrd" }; + var fs = try InitrdFS.init(ramdisk_module, std.testing.allocator); + defer fs.deinit(); + + vfs.setRoot(fs.root_node); + + var file1 = try vfs.openFile("/test1.txt", .NO_CREATION); + defer file1.close(); + + const bytes1 = try file1.read(128); + defer std.testing.allocator.free(bytes1); + + expectEqualSlices(u8, bytes1, "This is a test"); + + const bytes2 = try file1.read(5); + defer std.testing.allocator.free(bytes2); + + expectEqualSlices(u8, bytes2, "This "); +} + +test "read a file, out of memory" { + var fa = std.testing.FailingAllocator.init(std.testing.allocator, 6); + + var ramdisk_mem = try createInitrd(std.testing.allocator); + defer std.testing.allocator.free(ramdisk_mem); + + const ramdisk_range = .{ .start = @ptrToInt(ramdisk_mem.ptr), .end = @ptrToInt(ramdisk_mem.ptr) + ramdisk_mem.len }; + const ramdisk_module = .{ .region = ramdisk_range, .name = "ramdisk.initrd" }; + var fs = try InitrdFS.init(ramdisk_module, &fa.allocator); + defer fs.deinit(); + + vfs.setRoot(fs.root_node); + + var file1 = try vfs.openFile("/test1.txt", .NO_CREATION); + defer file1.close(); + + expectError(error.OutOfMemory, file1.read(1)); +} + +test "read a file, invalid/not opened/crafted *const Node" { + var ramdisk_mem = try createInitrd(std.testing.allocator); + defer std.testing.allocator.free(ramdisk_mem); + + const ramdisk_range = .{ .start = @ptrToInt(ramdisk_mem.ptr), .end = @ptrToInt(ramdisk_mem.ptr) + ramdisk_mem.len }; + const ramdisk_module = .{ .region = ramdisk_range, .name = "ramdisk.initrd" }; + var fs = try InitrdFS.init(ramdisk_module, std.testing.allocator); + defer fs.deinit(); + + vfs.setRoot(fs.root_node); + + // Open a valid file + var file1 = try vfs.openFile("/test1.txt", .NO_CREATION); + defer file1.close(); + + // Only one file open + expectEqual(fs.opened_files.items().len, 1); + + // Craft a Node + var fake_node = try std.testing.allocator.create(vfs.Node); + defer std.testing.allocator.destroy(fake_node); + fake_node.* = .{ .File = .{ .fs = fs.fs } }; + + expectError(error.NotOpened, fake_node.File.read(128)); + + // Still only one file open + expectEqual(fs.opened_files.items().len, 1); +} + +test "write does nothing" { + var ramdisk_mem = try createInitrd(std.testing.allocator); + defer std.testing.allocator.free(ramdisk_mem); + + const ramdisk_range = .{ .start = @ptrToInt(ramdisk_mem.ptr), .end = @ptrToInt(ramdisk_mem.ptr) + ramdisk_mem.len }; + const ramdisk_module = .{ .region = ramdisk_range, .name = "ramdisk.initrd" }; + var fs = try InitrdFS.init(ramdisk_module, std.testing.allocator); + defer fs.deinit(); + + vfs.setRoot(fs.root_node); + + // Open a valid file + var file1 = try vfs.openFile("/test1.txt", .NO_CREATION); + defer file1.close(); + + try file1.write("Blah"); + + // Unchanged file content + expectEqualSlices(u8, fs.opened_files.get(@ptrCast(*const vfs.Node, file1)).?.content, "This is a test"); +} + +/// See std.testing.expectEqualSlices. As need our panic. +fn expectEqualSlicesClone(comptime T: type, expected: []const T, actual: []const T) void { + if (expected.len != actual.len) { + panic(@errorReturnTrace(), "slice lengths differ. expected {}, found {}", .{ expected.len, actual.len }); + } + var i: usize = 0; + while (i < expected.len) : (i += 1) { + if (!std.meta.eql(expected[i], actual[i])) { + panic(@errorReturnTrace(), "index {} incorrect. expected {}, found {}", .{ i, expected[i], actual[i] }); + } + } +} + +/// +/// Test that we can open, read and close a file +/// +/// Arguments: +/// IN allocator: *Allocator - The allocator used for reading. +/// +fn rt_openReadClose(allocator: *Allocator) void { + const f1 = vfs.openFile("/ramdisk_test1.txt", .NO_CREATION) catch |e| { + panic(@errorReturnTrace(), "FAILURE: Failed to open file: {}\n", .{e}); + }; + const bytes1 = f1.read(128) catch |e| { + panic(@errorReturnTrace(), "FAILURE: Failed to read file: {}\n", .{e}); + }; + defer f1.close(); + expectEqualSlicesClone(u8, bytes1, "Testing ram disk"); + + const f2 = vfs.openFile("/ramdisk_test2.txt", .NO_CREATION) catch |e| { + panic(@errorReturnTrace(), "Failed to open file: {}\n", .{e}); + }; + const bytes2 = f2.read(128) catch |e| { + panic(@errorReturnTrace(), "FAILURE: Failed to read file: {}\n", .{e}); + }; + defer f2.close(); + expectEqualSlicesClone(u8, bytes2, "Testing ram disk for the second time"); + + // Try open a non-existent file + _ = vfs.openFile("/nope.txt", .NO_CREATION) catch |e| switch (e) { + error.NoSuchFileOrDir => {}, + else => panic(@errorReturnTrace(), "FAILURE: Expected error\n", .{}), + }; + std.log.info(.initrd, "Opened, read and closed\n", .{}); +} + +/// +/// The ramdisk runtime tests that will test the ramdisks functionality. +/// +/// Arguments: +/// IN rd_fs: *InitrdFS - The initialised ramdisk to play with. +/// +fn runtimeTests(rd_fs: *InitrdFS) void { + // There will be test files provided for the runtime tests + // Need to init the VFS. This will be overridden after the tests. + vfs.setRoot(rd_fs.root_node); + rt_openReadClose(rd_fs.allocator); + if (rd_fs.opened_files.items().len != 0) { + panic(@errorReturnTrace(), "FAILURE: Didn't close all files\n", .{}); + } +} diff --git a/src/kernel/kmain.zig b/src/kernel/kmain.zig index ac09fc5..159e24c 100644 --- a/src/kernel/kmain.zig +++ b/src/kernel/kmain.zig @@ -15,6 +15,8 @@ const panic_root = if (is_test) @import(mock_path ++ "panic_mock.zig") else @imp const task = if (is_test) @import(mock_path ++ "task_mock.zig") else @import("task.zig"); const heap = @import("heap.zig"); const scheduler = @import("scheduler.zig"); +const vfs = @import("vfs.zig"); +const initrd = @import("initrd.zig"); comptime { if (!is_test) { @@ -67,7 +69,7 @@ export fn kmain(boot_payload: arch.BootPayload) void { var fixed_allocator = mem_profile.fixed_allocator; panic_root.init(&mem_profile, &fixed_allocator.allocator) catch |e| { - panic_root.panic(@errorReturnTrace(), "Failed to initialise panic: {}", .{e}); + panic_root.panic(@errorReturnTrace(), "Failed to initialise panic: {}\n", .{e}); }; pmm.init(&mem_profile, &fixed_allocator.allocator); @@ -92,9 +94,35 @@ export fn kmain(boot_payload: arch.BootPayload) void { tty.init(&kernel_heap.allocator, boot_payload); scheduler.init(&kernel_heap.allocator) catch |e| { - panic_root.panic(@errorReturnTrace(), "Failed to initialise scheduler: {}", .{e}); + panic_root.panic(@errorReturnTrace(), "Failed to initialise scheduler: {}\n", .{e}); }; + // Get the ramdisk module + const rd_module = for (mem_profile.modules) |module| { + if (std.mem.eql(u8, module.name, "initrd.ramdisk")) { + break module; + } + } else null; + + if (rd_module) |module| { + // Load the ram disk + var ramdisk_filesystem = initrd.InitrdFS.init(module, &kernel_heap.allocator) catch |e| { + panic_root.panic(@errorReturnTrace(), "Failed to initialise ramdisk: {}\n", .{e}); + }; + defer { + ramdisk_filesystem.deinit(); + // Free the raw ramdisk module as we are done + kernel_vmm.free(module.region.start) catch |e| { + panic_root.panic(@errorReturnTrace(), "Failed to free ramdisk: {}\n", .{e}); + }; + } + + // Need to init the vfs after the ramdisk as we need the root node from the ramdisk filesystem + vfs.setRoot(ramdisk_filesystem.root_node); + + // Load all files here + } + // Initialisation is finished, now does other stuff std.log.info(.kmain, "Init\n", .{}); @@ -105,10 +133,10 @@ export fn kmain(boot_payload: arch.BootPayload) void { // Create a init2 task var idle_task = task.Task.create(initStage2, &kernel_heap.allocator) catch |e| { - panic_root.panic(@errorReturnTrace(), "Failed to create init stage 2 task: {}", .{e}); + panic_root.panic(@errorReturnTrace(), "Failed to create init stage 2 task: {}\n", .{e}); }; scheduler.scheduleTask(idle_task, &kernel_heap.allocator) catch |e| { - panic_root.panic(@errorReturnTrace(), "Failed to schedule init stage 2 task: {}", .{e}); + panic_root.panic(@errorReturnTrace(), "Failed to schedule init stage 2 task: {}\n", .{e}); }; // Can't return for now, later this can return maybe diff --git a/src/kernel/vfs.zig b/src/kernel/vfs.zig index e343e34..452b916 100644 --- a/src/kernel/vfs.zig +++ b/src/kernel/vfs.zig @@ -5,7 +5,7 @@ const ArrayList = std.ArrayList; const Allocator = std.mem.Allocator; /// Flags specifying what to do when opening a file or directory -const OpenFlags = enum { +pub const OpenFlags = enum { /// Create a directory if it doesn't exist CREATE_DIR, /// Create a file if it doesn't exist @@ -79,10 +79,11 @@ pub const FileSystem = struct { /// Return: []u8 /// The data read as a slice of bytes. The length will be <= len, including 0 if there was no data to read /// - /// Error: Allocator.Error + /// Error: Allocator.Error || Error /// Allocator.Error.OutOfMemory - There wasn't enough memory to fulfill the request + /// Error.NotOpened - If the node provided is not one that the file system recognised as being opened. /// - const Read = fn (self: *const Self, node: *const FileNode, len: usize) Allocator.Error![]u8; + const Read = fn (self: *const Self, node: *const FileNode, len: usize) (Allocator.Error || Error)![]u8; /// /// Write to an open file @@ -95,7 +96,7 @@ pub const FileSystem = struct { /// Error: Allocator.Error /// Allocator.Error.OutOfMemory - There wasn't enough memory to fulfill the request /// - const Write = fn (self: *const Self, node: *const FileNode, bytes: []const u8) Allocator.Error!void; + const Write = fn (self: *const Self, node: *const FileNode, bytes: []const u8) (Allocator.Error || Error)!void; /// /// Open a file/dir within the filesystem. The result can then be used for write, read or close operations @@ -152,7 +153,7 @@ pub const FileNode = struct { fs: *const FileSystem, /// See the documentation for FileSystem.Read - pub fn read(self: *const FileNode, len: usize) Allocator.Error![]u8 { + pub fn read(self: *const FileNode, len: usize) (Allocator.Error || Error)![]u8 { return self.fs.read(self.fs, self, len); } @@ -162,7 +163,7 @@ pub const FileNode = struct { } /// See the documentation for FileSystem.Write - pub fn write(self: *const FileNode, bytes: []const u8) Allocator.Error!void { + pub fn write(self: *const FileNode, bytes: []const u8) (Allocator.Error || Error)!void { return self.fs.write(self.fs, self, bytes); } }; @@ -203,6 +204,9 @@ pub const Error = error{ /// The flags provided are invalid for the requested operation InvalidFlags, + + /// The node is not recognised as being opened by the filesystem + NotOpened, }; /// Errors that can be thrown when attempting to mount @@ -383,6 +387,16 @@ pub fn isAbsolute(path: []const u8) bool { return path.len > 0 and path[0] == SEPARATOR; } +/// +/// Initialise the virtual file system with a root Node. This will be a Directory node. +/// +/// Arguments: +/// IN node: *Node - The node to initialise the root node. +/// +pub fn setRoot(node: *Node) void { + root = node; +} + const TestFS = struct { const TreeNode = struct { val: *Node, @@ -453,7 +467,7 @@ const TestFS = struct { fn close(fs: *const FileSystem, node: *const FileNode) void {} - fn read(fs: *const FileSystem, node: *const FileNode, len: usize) Allocator.Error![]u8 { + fn read(fs: *const FileSystem, node: *const FileNode, len: usize) (Allocator.Error || Error)![]u8 { var test_fs = @fieldParentPtr(TestFS, "instance", fs.instance); // Get the tree that corresponds to the node. Cannot error as the file is already open so it does exist var tree = (getTreeNode(test_fs, node) catch unreachable) orelse unreachable; @@ -464,7 +478,7 @@ const TestFS = struct { return bytes; } - fn write(fs: *const FileSystem, node: *const FileNode, bytes: []const u8) Allocator.Error!void { + fn write(fs: *const FileSystem, node: *const FileNode, bytes: []const u8) (Allocator.Error || Error)!void { var test_fs = @fieldParentPtr(TestFS, "instance", fs.instance); var tree = (try getTreeNode(test_fs, node)) orelse unreachable; if (tree.data) |_| { diff --git a/src/kernel/vmm.zig b/src/kernel/vmm.zig index 36ad9bc..841f306 100644 --- a/src/kernel/vmm.zig +++ b/src/kernel/vmm.zig @@ -290,7 +290,9 @@ pub fn VirtualMemoryManager(comptime Payload: type) type { const addr = pmm.alloc() orelse unreachable; try block_list.append(addr); // The map function failing isn't the caller's responsibility so panic as it shouldn't happen - self.mapper.mapFn(vaddr, vaddr + BLOCK_SIZE, addr, addr + BLOCK_SIZE, attrs, self.allocator, self.payload) catch |e| panic(@errorReturnTrace(), "Failed to map virtual memory: {}\n", .{e}); + self.mapper.mapFn(vaddr, vaddr + BLOCK_SIZE, addr, addr + BLOCK_SIZE, attrs, self.allocator, self.payload) catch |e| { + panic(@errorReturnTrace(), "Failed to map virtual memory: {}\n", .{e}); + }; vaddr += BLOCK_SIZE; } _ = try self.allocations.put(vaddr_start, Allocation{ .physical = block_list }); @@ -327,8 +329,8 @@ pub fn VirtualMemoryManager(comptime Payload: type) type { }; } // Unmap the entire range - const region_start = entry * BLOCK_SIZE; - const region_end = (entry + num_physical_allocations) * BLOCK_SIZE; + const region_start = vaddr; + const region_end = vaddr + (num_physical_allocations * BLOCK_SIZE); self.mapper.unmapFn(region_start, region_end, self.payload) catch |e| { panic(@errorReturnTrace(), "Failed to unmap VMM reserved memory from 0x{X} to 0x{X}: {}\n", .{ region_start, region_end, e }); }; @@ -412,7 +414,7 @@ test "alloc and free" { } // Make sure that the entries are set or not depending on the allocation success - var vaddr = entry * BLOCK_SIZE; + var vaddr = vmm.start + entry * BLOCK_SIZE; while (vaddr < (entry + num_to_alloc) * BLOCK_SIZE) : (vaddr += BLOCK_SIZE) { if (should_be_set) { // Allocation succeeded so this address should be set @@ -471,10 +473,10 @@ test "set" { const num_entries = 512; var vmm = try testInit(num_entries); - const vstart = BLOCK_SIZE * 37; - const vend = BLOCK_SIZE * 46; - const pstart = vstart + 123; - const pend = vend + 123; + const vstart = vmm.start + BLOCK_SIZE * 37; + const vend = vmm.start + BLOCK_SIZE * 46; + const pstart = BLOCK_SIZE * 37 + 123; + const pend = BLOCK_SIZE * 46 + 123; const attrs = Attributes{ .kernel = true, .writable = true, .cachable = true }; try vmm.set(.{ .start = vstart, .end = vend }, mem.Range{ .start = pstart, .end = pend }, attrs); // Make sure it put the correct address in the map @@ -484,20 +486,21 @@ test "set" { // The entries before the virtual start shouldn't be set var vaddr = vmm.start; while (vaddr < vstart) : (vaddr += BLOCK_SIZE) { - std.testing.expect(!(try allocations.isSet(vaddr / BLOCK_SIZE))); + std.testing.expect(!(try allocations.isSet((vaddr - vmm.start) / BLOCK_SIZE))); } // The entries up until the virtual end should be set while (vaddr < vend) : (vaddr += BLOCK_SIZE) { - std.testing.expect(try allocations.isSet(vaddr / BLOCK_SIZE)); + std.testing.expect(try allocations.isSet((vaddr - vmm.start) / BLOCK_SIZE)); } // The entries after the virtual end should not be set while (vaddr < vmm.end) : (vaddr += BLOCK_SIZE) { - std.testing.expect(!(try allocations.isSet(vaddr / BLOCK_SIZE))); + std.testing.expect(!(try allocations.isSet((vaddr - vmm.start) / BLOCK_SIZE))); } } var test_allocations: ?bitmap.Bitmap(u64) = null; var test_mapper = Mapper(u8){ .mapFn = testMap, .unmapFn = testUnmap }; +const test_vaddr_start: usize = 0xC0000000; /// /// Initialise a virtual memory manager used for testing @@ -533,7 +536,7 @@ fn testInit(num_entries: u32) Allocator.Error!VirtualMemoryManager(u8) { .modules = &[_]mem.Module{}, }; pmm.init(&mem_profile, std.heap.page_allocator); - return VirtualMemoryManager(u8).init(0, num_entries * BLOCK_SIZE, std.heap.page_allocator, test_mapper, 39); + return VirtualMemoryManager(u8).init(test_vaddr_start, test_vaddr_start + num_entries * BLOCK_SIZE, std.heap.page_allocator, test_mapper, 39); } /// @@ -552,7 +555,7 @@ fn testMap(vstart: usize, vend: usize, pstart: usize, pend: usize, attrs: Attrib std.testing.expectEqual(@as(u8, 39), payload); var vaddr = vstart; while (vaddr < vend) : (vaddr += BLOCK_SIZE) { - (test_allocations.?).setEntry(vaddr / BLOCK_SIZE) catch unreachable; + (test_allocations.?).setEntry((vaddr - test_vaddr_start) / BLOCK_SIZE) catch unreachable; } } @@ -568,7 +571,11 @@ fn testUnmap(vstart: usize, vend: usize, payload: u8) (Allocator.Error || Mapper std.testing.expectEqual(@as(u8, 39), payload); var vaddr = vstart; while (vaddr < vend) : (vaddr += BLOCK_SIZE) { - (test_allocations.?).clearEntry(vaddr / BLOCK_SIZE) catch unreachable; + if ((test_allocations.?).isSet((vaddr - test_vaddr_start) / BLOCK_SIZE) catch unreachable) { + (test_allocations.?).clearEntry((vaddr - test_vaddr_start) / BLOCK_SIZE) catch unreachable; + } else { + return MapperError.NotMapped; + } } } diff --git a/test/ramdisk_test1.txt b/test/ramdisk_test1.txt new file mode 100644 index 0000000..023ca16 --- /dev/null +++ b/test/ramdisk_test1.txt @@ -0,0 +1 @@ +Testing ram disk \ No newline at end of file diff --git a/test/ramdisk_test2.txt b/test/ramdisk_test2.txt new file mode 100644 index 0000000..07fd680 --- /dev/null +++ b/test/ramdisk_test2.txt @@ -0,0 +1 @@ +Testing ram disk for the second time \ No newline at end of file