
Added finding the next free cluster Added name to long name Added long name to short name Added tests for above Added createLongNameEntry + tests Moved tests to bottom Added createShortNameEntry + test Used the RTC for the date and time for the created short entry Tidied createEntries A bit of refactor No symlinks for FAT32 findNextFreeCluster updates FAT Reordered tests to better follow the FAT32FS code FAT32 has no support for symlinks, so removed code around this. Removed open_args from createNode as it doesn't need it Added writeEntries + tests Write the short and long entries to disk findNextFreeCluster update cluster chain with parent cluster Added FAT32 write + tests This Added the ability to create files and directories and write to files. Added location of the short dir entry for the file so can update the size of the file on disk Added folders to the test FAT32 directory. Also fixed minor bug in mkfat32 Added check for destroying the filesystem Fixed error message for cluster size Simpler if condition 0x0FFFFFFF => 0xFFFFFFFF Spelling Fixed test
673 lines
25 KiB
Zig
673 lines
25 KiB
Zig
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 log = std.log.scoped(.initrd);
|
|
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 = @import("../mem.zig");
|
|
const panic = @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 for the name and file content.
|
|
const InitrdHeader = struct {
|
|
/// The name of the file
|
|
name: []u8,
|
|
|
|
/// The content of the file
|
|
content: []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 files.
|
|
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.Node) void {
|
|
var self = @fieldParentPtr(InitrdFS, "instance", fs.instance);
|
|
// 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.remove(node)) |entry_node| {
|
|
self.allocator.destroy(node);
|
|
}
|
|
}
|
|
|
|
/// See vfs.FileSystem.read
|
|
fn read(fs: *const vfs.FileSystem, file_node: *const vfs.FileNode, bytes: []u8) (Allocator.Error || vfs.Error)!usize {
|
|
var self = @fieldParentPtr(InitrdFS, "instance", fs.instance);
|
|
const node = @ptrCast(*const vfs.Node, file_node);
|
|
const file_header = self.opened_files.get(node) orelse return vfs.Error.NotOpened;
|
|
const length = std.math.min(bytes.len, file_header.content.len);
|
|
std.mem.copy(u8, bytes, file_header.content[0..length]);
|
|
return length;
|
|
}
|
|
|
|
/// See vfs.FileSystem.write
|
|
fn write(fs: *const vfs.FileSystem, node: *const vfs.FileNode, bytes: []const u8) (Allocator.Error || vfs.Error)!usize {
|
|
return 0;
|
|
}
|
|
|
|
/// See vfs.FileSystem.open
|
|
fn open(fs: *const vfs.FileSystem, dir: *const vfs.DirNode, name: []const u8, flags: vfs.OpenFlags, args: vfs.OpenArgs) (Allocator.Error || vfs.Error)!*vfs.Node {
|
|
var self = @fieldParentPtr(InitrdFS, "instance", fs.instance);
|
|
switch (flags) {
|
|
.CREATE_DIR, .CREATE_FILE, .CREATE_SYMLINK => 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 node = try self.allocator.create(vfs.Node);
|
|
errdefer self.allocator.destroy(node);
|
|
node.* = .{ .File = .{ .fs = self.fs } };
|
|
try self.opened_files.put(node, file);
|
|
return 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.count() == 0);
|
|
self.allocator.destroy(self.root_node);
|
|
self.allocator.destroy(self.fs);
|
|
self.opened_files.deinit();
|
|
for (self.files) |entry| {
|
|
self.allocator.free(entry.name);
|
|
self.allocator.free(entry.content);
|
|
}
|
|
self.allocator.free(self.files);
|
|
self.allocator.destroy(self);
|
|
}
|
|
|
|
///
|
|
/// Initialise a ramdisk file system from a raw ramdisk in memory provided by the bootloader in a stream.
|
|
/// Any memory allocated will be freed.
|
|
///
|
|
/// Arguments:
|
|
/// IN stream: *std.io.FixedBufferStream([]u8) - The stream that contains the raw ramdisk data.
|
|
/// IN allocator: *Allocator - The allocator used for initialising any memory needed.
|
|
///
|
|
/// Return: *InitrdFS
|
|
/// A pointer to the ram disk file system.
|
|
///
|
|
/// Error: Error || error{EndOfStream} || Allocator.Error || std.io.FixedBufferStream([]u8).ReadError
|
|
/// 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.
|
|
/// error.EndOfStream - When reading from the stream, we reach the end of the stream
|
|
/// before completing the read.
|
|
/// error.OutOfMemory - If there isn't enough memory for initialisation. Any memory
|
|
/// allocated will be freed.
|
|
///
|
|
pub fn init(stream: *std.io.FixedBufferStream([]u8), allocator: *Allocator) (Error || error{EndOfStream} || Allocator.Error)!*InitrdFS {
|
|
log.info("Init\n", .{});
|
|
defer log.info("Done\n", .{});
|
|
|
|
// First @sizeOf(usize) bytes is the number of files
|
|
const num_of_files = try stream.reader().readIntNative(usize);
|
|
var headers = try allocator.alloc(InitrdHeader, num_of_files);
|
|
errdefer allocator.free(headers);
|
|
|
|
// Populate the headers
|
|
var i: usize = 0;
|
|
|
|
// If we error, then free any headers that we allocated.
|
|
errdefer {
|
|
var j: usize = 0;
|
|
while (j < i) : (j += 1) {
|
|
allocator.free(headers[j].name);
|
|
allocator.free(headers[j].content);
|
|
}
|
|
}
|
|
|
|
while (i < num_of_files) : (i += 1) {
|
|
// We don't need to store the lengths any more as we have the slice.len
|
|
const name_len = try stream.reader().readIntNative(usize);
|
|
if (name_len == 0) {
|
|
return Error.InvalidRamDisk;
|
|
}
|
|
headers[i].name = try allocator.alloc(u8, name_len);
|
|
errdefer allocator.free(headers[i].name);
|
|
if ((try stream.reader().readAll(headers[i].name)) != name_len) {
|
|
return Error.InvalidRamDisk;
|
|
}
|
|
const content_len = try stream.reader().readIntNative(usize);
|
|
if (content_len == 0) {
|
|
return Error.InvalidRamDisk;
|
|
}
|
|
headers[i].content = try allocator.alloc(u8, content_len);
|
|
errdefer allocator.free(headers[i].content);
|
|
if ((try stream.reader().readAll(headers[i].content)) != content_len) {
|
|
return Error.InvalidRamDisk;
|
|
}
|
|
}
|
|
|
|
// If we aren't at the end, error.
|
|
if ((try stream.getPos()) != (try stream.getEndPos())) {
|
|
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 || std.io.FixedBufferStream([]u8).WriteError)![]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" };
|
|
|
|
// Ensure these two arrays are the same length
|
|
std.debug.assert(file_names.len == file_contents.len);
|
|
|
|
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_bytes = try allocator.alloc(u8, total_ramdisk_len);
|
|
var ramdisk_stream = std.io.fixedBufferStream(ramdisk_bytes);
|
|
|
|
// Copy the data into the allocated memory
|
|
try ramdisk_stream.writer().writeIntNative(usize, file_names.len);
|
|
inline for ([_]usize{ 0, 1, 2 }) |i| {
|
|
// Name len
|
|
try ramdisk_stream.writer().writeIntNative(usize, file_names[i].len);
|
|
// Name
|
|
try ramdisk_stream.writer().writeAll(file_names[i]);
|
|
// File len
|
|
try ramdisk_stream.writer().writeIntNative(usize, file_contents[i].len);
|
|
// File content
|
|
try ramdisk_stream.writer().writeAll(file_contents[i]);
|
|
}
|
|
// Make sure we are full
|
|
expectEqual(try ramdisk_stream.getPos(), total_ramdisk_len);
|
|
expectEqual(try ramdisk_stream.getPos(), try ramdisk_stream.getEndPos());
|
|
return ramdisk_bytes;
|
|
}
|
|
|
|
test "init with files valid" {
|
|
var ramdisk_bytes = try createInitrd(std.testing.allocator);
|
|
defer std.testing.allocator.free(ramdisk_bytes);
|
|
|
|
var initrd_stream = std.io.fixedBufferStream(ramdisk_bytes);
|
|
var fs = try InitrdFS.init(&initrd_stream, 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.count(), 0);
|
|
}
|
|
|
|
test "init with files invalid - invalid number of files" {
|
|
var ramdisk_bytes = try createInitrd(std.testing.allocator);
|
|
// Override the number of files
|
|
std.mem.writeIntSlice(usize, ramdisk_bytes[0..], 10, builtin.endian);
|
|
defer std.testing.allocator.free(ramdisk_bytes);
|
|
|
|
var initrd_stream = std.io.fixedBufferStream(ramdisk_bytes);
|
|
expectError(error.InvalidRamDisk, InitrdFS.init(&initrd_stream, std.testing.allocator));
|
|
|
|
// Override the number of files
|
|
std.mem.writeIntSlice(usize, ramdisk_bytes[0..], 0, builtin.endian);
|
|
expectError(error.InvalidRamDisk, InitrdFS.init(&initrd_stream, 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_bytes = try createInitrd(std.testing.allocator);
|
|
// Override the first file name length, make is shorter
|
|
std.mem.writeIntSlice(usize, ramdisk_bytes[4..], 2, builtin.endian);
|
|
defer std.testing.allocator.free(ramdisk_bytes);
|
|
|
|
var initrd_stream = std.io.fixedBufferStream(ramdisk_bytes);
|
|
expectError(error.InvalidRamDisk, InitrdFS.init(&initrd_stream, std.testing.allocator));
|
|
}
|
|
|
|
{
|
|
var ramdisk_bytes = try createInitrd(std.testing.allocator);
|
|
// Override the first file name length, make is 4 shorter
|
|
std.mem.writeIntSlice(usize, ramdisk_bytes[4..], 5, builtin.endian);
|
|
// Override the second file name length, make is 4 longer
|
|
std.mem.writeIntSlice(usize, ramdisk_bytes[35..], 13, builtin.endian);
|
|
defer std.testing.allocator.free(ramdisk_bytes);
|
|
|
|
var initrd_stream = std.io.fixedBufferStream(ramdisk_bytes);
|
|
expectError(error.InvalidRamDisk, InitrdFS.init(&initrd_stream, std.testing.allocator));
|
|
}
|
|
}
|
|
|
|
/// The number of allocations that the init function make.
|
|
const init_allocations: usize = 10;
|
|
|
|
test "init with files cleans memory if OutOfMemory" {
|
|
var i: usize = 0;
|
|
while (i < init_allocations) : (i += 1) {
|
|
var fa = std.testing.FailingAllocator.init(std.testing.allocator, i);
|
|
|
|
var ramdisk_bytes = try createInitrd(std.testing.allocator);
|
|
defer std.testing.allocator.free(ramdisk_bytes);
|
|
|
|
var initrd_stream = std.io.fixedBufferStream(ramdisk_bytes);
|
|
expectError(error.OutOfMemory, InitrdFS.init(&initrd_stream, &fa.allocator));
|
|
}
|
|
}
|
|
|
|
test "getRootNode" {
|
|
var ramdisk_bytes = try createInitrd(std.testing.allocator);
|
|
defer std.testing.allocator.free(ramdisk_bytes);
|
|
|
|
var initrd_stream = std.io.fixedBufferStream(ramdisk_bytes);
|
|
var fs = try InitrdFS.init(&initrd_stream, std.testing.allocator);
|
|
defer fs.deinit();
|
|
|
|
expectEqual(fs.fs.getRootNode(fs.fs), &fs.root_node.Dir);
|
|
}
|
|
|
|
test "open valid file" {
|
|
var ramdisk_bytes = try createInitrd(std.testing.allocator);
|
|
defer std.testing.allocator.free(ramdisk_bytes);
|
|
|
|
var initrd_stream = std.io.fixedBufferStream(ramdisk_bytes);
|
|
var fs = try InitrdFS.init(&initrd_stream, std.testing.allocator);
|
|
defer fs.deinit();
|
|
|
|
try vfs.setRoot(fs.root_node);
|
|
|
|
var file1 = try vfs.openFile("/test1.txt", .NO_CREATION);
|
|
defer file1.close();
|
|
|
|
expectEqual(fs.opened_files.count(), 1);
|
|
expectEqualSlices(u8, fs.opened_files.get(@ptrCast(*const vfs.Node, file1)).?.name, "test1.txt");
|
|
|
|
var file3_node = try vfs.open("/test3.txt", true, .NO_CREATION, .{});
|
|
defer file3_node.File.close();
|
|
|
|
expectEqual(fs.opened_files.count(), 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.count(), 3);
|
|
}
|
|
|
|
test "open fail with invalid flags" {
|
|
var ramdisk_bytes = try createInitrd(std.testing.allocator);
|
|
defer std.testing.allocator.free(ramdisk_bytes);
|
|
|
|
var initrd_stream = std.io.fixedBufferStream(ramdisk_bytes);
|
|
var fs = try InitrdFS.init(&initrd_stream, std.testing.allocator);
|
|
defer fs.deinit();
|
|
|
|
try 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.openFile("/text10.txt", .CREATE_SYMLINK));
|
|
expectError(error.InvalidFlags, vfs.openDir("/text10.txt", .CREATE_DIR));
|
|
expectError(error.InvalidFlags, vfs.openDir("/text10.txt", .CREATE_FILE));
|
|
expectError(error.InvalidFlags, vfs.openDir("/text10.txt", .CREATE_SYMLINK));
|
|
expectError(error.InvalidFlags, vfs.openFile("/test/", .CREATE_DIR));
|
|
expectError(error.InvalidFlags, vfs.openFile("/test/", .CREATE_FILE));
|
|
expectError(error.InvalidFlags, vfs.openFile("/test/", .CREATE_SYMLINK));
|
|
expectError(error.InvalidFlags, vfs.openDir("/test/", .CREATE_DIR));
|
|
expectError(error.InvalidFlags, vfs.openDir("/test/", .CREATE_FILE));
|
|
expectError(error.InvalidFlags, vfs.openDir("/test/", .CREATE_SYMLINK));
|
|
expectError(error.InvalidFlags, vfs.openSymlink("/test/", "", .CREATE_FILE));
|
|
expectError(error.InvalidFlags, vfs.openSymlink("/test/", "", .CREATE_DIR));
|
|
expectError(error.InvalidFlags, vfs.openSymlink("/test/", "", .CREATE_SYMLINK));
|
|
}
|
|
|
|
test "open fail with NoSuchFileOrDir" {
|
|
var ramdisk_bytes = try createInitrd(std.testing.allocator);
|
|
defer std.testing.allocator.free(ramdisk_bytes);
|
|
|
|
var initrd_stream = std.io.fixedBufferStream(ramdisk_bytes);
|
|
var fs = try InitrdFS.init(&initrd_stream, std.testing.allocator);
|
|
defer fs.deinit();
|
|
|
|
try vfs.setRoot(fs.root_node);
|
|
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, init_allocations);
|
|
|
|
var ramdisk_bytes = try createInitrd(std.testing.allocator);
|
|
defer std.testing.allocator.free(ramdisk_bytes);
|
|
|
|
var initrd_stream = std.io.fixedBufferStream(ramdisk_bytes);
|
|
var fs = try InitrdFS.init(&initrd_stream, &fa.allocator);
|
|
defer fs.deinit();
|
|
|
|
try vfs.setRoot(fs.root_node);
|
|
|
|
expectError(error.OutOfMemory, vfs.openFile("/test1.txt", .NO_CREATION));
|
|
}
|
|
|
|
test "open two of the same file" {
|
|
var ramdisk_bytes = try createInitrd(std.testing.allocator);
|
|
defer std.testing.allocator.free(ramdisk_bytes);
|
|
|
|
var initrd_stream = std.io.fixedBufferStream(ramdisk_bytes);
|
|
var fs = try InitrdFS.init(&initrd_stream, std.testing.allocator);
|
|
defer fs.deinit();
|
|
|
|
try 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.count(), 2);
|
|
expect(file1 != file2);
|
|
|
|
var b1: [128]u8 = undefined;
|
|
const length1 = try file1.read(b1[0..b1.len]);
|
|
var b2: [128]u8 = undefined;
|
|
const length2 = try file2.read(b2[0..b2.len]);
|
|
expectEqualSlices(u8, b1[0..length1], b2[0..length2]);
|
|
}
|
|
|
|
test "close a file" {
|
|
var ramdisk_bytes = try createInitrd(std.testing.allocator);
|
|
defer std.testing.allocator.free(ramdisk_bytes);
|
|
|
|
var initrd_stream = std.io.fixedBufferStream(ramdisk_bytes);
|
|
var fs = try InitrdFS.init(&initrd_stream, std.testing.allocator);
|
|
defer fs.deinit();
|
|
|
|
try 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.count(), 1);
|
|
|
|
var file3_node = try vfs.open("/test3.txt", true, .NO_CREATION, .{});
|
|
|
|
expectEqual(fs.opened_files.count(), 2);
|
|
file1.close();
|
|
expectEqual(fs.opened_files.count(), 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.count(), 2);
|
|
file3_node.File.close();
|
|
expectEqual(fs.opened_files.count(), 1);
|
|
}
|
|
|
|
test "close a non-opened file" {
|
|
var ramdisk_bytes = try createInitrd(std.testing.allocator);
|
|
defer std.testing.allocator.free(ramdisk_bytes);
|
|
|
|
var initrd_stream = std.io.fixedBufferStream(ramdisk_bytes);
|
|
var fs = try InitrdFS.init(&initrd_stream, std.testing.allocator);
|
|
defer fs.deinit();
|
|
|
|
try 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.count(), 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.count(), 1);
|
|
}
|
|
|
|
test "read a file" {
|
|
var ramdisk_bytes = try createInitrd(std.testing.allocator);
|
|
defer std.testing.allocator.free(ramdisk_bytes);
|
|
|
|
var initrd_stream = std.io.fixedBufferStream(ramdisk_bytes);
|
|
var fs = try InitrdFS.init(&initrd_stream, std.testing.allocator);
|
|
defer fs.deinit();
|
|
|
|
try vfs.setRoot(fs.root_node);
|
|
|
|
var file1 = try vfs.openFile("/test1.txt", .NO_CREATION);
|
|
defer file1.close();
|
|
|
|
var bytes1: [128]u8 = undefined;
|
|
const length1 = try file1.read(bytes1[0..bytes1.len]);
|
|
|
|
expectEqualSlices(u8, bytes1[0..length1], "This is a test");
|
|
|
|
var bytes2: [5]u8 = undefined;
|
|
const length2 = try file1.read(bytes2[0..bytes2.len]);
|
|
|
|
expectEqualSlices(u8, bytes2[0..length2], "This ");
|
|
}
|
|
|
|
test "read a file, invalid/not opened/crafted *const Node" {
|
|
var ramdisk_bytes = try createInitrd(std.testing.allocator);
|
|
defer std.testing.allocator.free(ramdisk_bytes);
|
|
|
|
var initrd_stream = std.io.fixedBufferStream(ramdisk_bytes);
|
|
var fs = try InitrdFS.init(&initrd_stream, std.testing.allocator);
|
|
defer fs.deinit();
|
|
|
|
try 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.count(), 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 } };
|
|
|
|
var unused: [1]u8 = undefined;
|
|
expectError(error.NotOpened, fake_node.File.read(unused[0..unused.len]));
|
|
|
|
// Still only one file open
|
|
expectEqual(fs.opened_files.count(), 1);
|
|
}
|
|
|
|
test "write does nothing" {
|
|
var ramdisk_bytes = try createInitrd(std.testing.allocator);
|
|
defer std.testing.allocator.free(ramdisk_bytes);
|
|
|
|
var initrd_stream = std.io.fixedBufferStream(ramdisk_bytes);
|
|
var fs = try InitrdFS.init(&initrd_stream, std.testing.allocator);
|
|
defer fs.deinit();
|
|
|
|
try vfs.setRoot(fs.root_node);
|
|
|
|
// Open a valid file
|
|
var file1 = try vfs.openFile("/test1.txt", .NO_CREATION);
|
|
defer file1.close();
|
|
|
|
expectEqual(@as(usize, 0), 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});
|
|
};
|
|
var bytes1: [128]u8 = undefined;
|
|
const length1 = f1.read(bytes1[0..bytes1.len]) catch |e| {
|
|
panic(@errorReturnTrace(), "FAILURE: Failed to read file: {}\n", .{e});
|
|
};
|
|
defer f1.close();
|
|
expectEqualSlicesClone(u8, bytes1[0..length1], "Testing ram disk");
|
|
|
|
const f2 = vfs.openFile("/ramdisk_test2.txt", .NO_CREATION) catch |e| {
|
|
panic(@errorReturnTrace(), "Failed to open file: {}\n", .{e});
|
|
};
|
|
var bytes2: [128]u8 = undefined;
|
|
const length2 = f2.read(bytes2[0..bytes2.len]) catch |e| {
|
|
panic(@errorReturnTrace(), "FAILURE: Failed to read file: {}\n", .{e});
|
|
};
|
|
defer f2.close();
|
|
expectEqualSlicesClone(u8, bytes2[0..length2], "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", .{}),
|
|
};
|
|
log.info("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) catch |e| {
|
|
panic(@errorReturnTrace(), "Ramdisk root node isn't a directory node: {}\n", .{e});
|
|
};
|
|
rt_openReadClose(rd_fs.allocator);
|
|
if (rd_fs.opened_files.count() != 0) {
|
|
panic(@errorReturnTrace(), "FAILURE: Didn't close all files\n", .{});
|
|
}
|
|
}
|