commit
18284daaa1
11 changed files with 900 additions and 36 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -27,6 +27,7 @@
|
|||
*.i*86
|
||||
*.x86_64
|
||||
*.hex
|
||||
*.initrd
|
||||
|
||||
# Debug files
|
||||
*.dSYM/
|
||||
|
|
135
build.zig
135
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;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
675
src/kernel/initrd.zig
Normal file
675
src/kernel/initrd.zig
Normal file
|
@ -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", .{});
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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) |_| {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
1
test/ramdisk_test1.txt
Normal file
1
test/ramdisk_test1.txt
Normal file
|
@ -0,0 +1 @@
|
|||
Testing ram disk
|
1
test/ramdisk_test2.txt
Normal file
1
test/ramdisk_test2.txt
Normal file
|
@ -0,0 +1 @@
|
|||
Testing ram disk for the second time
|
Loading…
Reference in a new issue