pluto/src/kernel/vfs.zig
DrDeano bcc1712737
Created ramdisk build step
Refactored tests for the scheduler and task


Revert "Refactored tests for the scheduler and task"

This reverts commit 2bf56a368bc18f2bd2d33c385e3672d07e4431d9.

Refactored tests for the scheduler and task


Task fmt


Task fmt again >:(


Ramdisk


Added NotOpened error for file read and write
Added vfs init to initialise the root node
Added the ramdisk.initrd file to grub
Update makeiso to copy the ramdisk to the modules folder
Add a ramdisk step to create a ramdisk to be leaded by grub and parsed by the kernel
Add test files for runtime tests of ramdisk

vfs.init => vfs.setRoot


Improved ramdisk step

Also spelling

Changed name for the initrd


Rename RamdiskFS => InitrdFS


Add deinit for initrd

Fixed VMM unmap

{}
2020-07-25 11:18:19 +01:00

750 lines
28 KiB
Zig

const std = @import("std");
const testing = std.testing;
const TailQueue = std.TailQueue;
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
/// Flags specifying what to do when opening a file or directory
pub const OpenFlags = enum {
/// Create a directory if it doesn't exist
CREATE_DIR,
/// Create a file if it doesn't exist
CREATE_FILE,
/// Do not create a file or directory
NO_CREATION,
};
/// A filesystem node that could either be a directory or a file
pub const Node = union(enum) {
/// The file node if this represents a file
File: FileNode,
/// The dir node if this represents a directory
Dir: DirNode,
const Self = @This();
///
/// Check if this node is a directory
///
/// Arguments:
/// IN self: Self - The node being checked
///
/// Return: bool
/// True if this is a directory else false
///
pub fn isDir(self: Self) bool {
return switch (self) {
.Dir => true,
.File => false,
};
}
///
/// Check if this node is a file
///
/// Arguments:
/// IN self: Self - The node being checked
///
/// Return: bool
/// True if this is a file else false
///
pub fn isFile(self: Self) bool {
return switch (self) {
.File => true,
.Dir => false,
};
}
};
/// The functions of a filesystem
pub const FileSystem = struct {
const Self = @This();
///
/// Close an open file, performing any last operations required to save data etc.
///
/// Arguments:
/// IN self: *const FileSystem - The filesystem in question being operated on.
/// IN node: *const FileNode - The file being closed
///
const Close = fn (self: *const Self, node: *const FileNode) void;
///
/// Read from an open file
///
/// Arguments:
/// IN self: *const FileSystem - The filesystem in question being operated on
/// IN node: *const FileNode - The file being read from
/// IN len: usize - The number of bytes to read from the file
///
/// 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.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 || Error)![]u8;
///
/// Write to an open file
///
/// Arguments:
/// IN self: *const FileSystem - The filesystem in question being operated on
/// IN node: *const FileNode - The file being read from
/// IN bytes: []u8 - The bytes to write to the file
///
/// 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 || Error)!void;
///
/// Open a file/dir within the filesystem. The result can then be used for write, read or close operations
///
/// Arguments:
/// IN self: *const FileSystem - The filesystem in question being operated on
/// IN node: *const DirNode - The directory under which to open the file/dir from
/// IN name: []const u8 - The name of the file to open
/// IN flags: OpenFlags - The flags to consult when opening the file
///
/// Return: *const Node
/// The node representing the file/dir opened
///
/// Error: Allocator.Error || Error
/// Allocator.Error.OutOfMemory - There wasn't enough memory to fulfill the request
/// Error.NoSuchFileOrDir - The file/dir by that name doesn't exist and the flags didn't specify to create it
///
const Open = fn (self: *const Self, node: *const DirNode, name: []const u8, flags: OpenFlags) (Allocator.Error || Error)!*Node;
///
/// Get the node representing the root of the filesystem. Used when mounting to bind the mount point to the root of the mounted fs
///
/// Arguments:
/// IN self: *const Self - The filesystem to get the root node for
///
/// Return: *const DirNode
/// The root directory node
///
const GetRootNode = fn (self: *const Self) *const DirNode;
/// The close function
close: Close,
/// The read function
read: Read,
/// The write function
write: Write,
/// The open function
open: Open,
/// The function for retrieving the root node
getRootNode: GetRootNode,
/// Points to a usize field within the underlying filesystem so that the close, read, write and open functions can access its low-level implementation using @fieldParentPtr. For example, this could point to a usize field within a FAT32 filesystem data structure, which stores all the data and state that is needed in order to interact with a physical disk
/// The value of instance is reserved for future use and so should be left as 0
instance: *usize,
};
/// A node representing a file within a filesystem
pub const FileNode = struct {
/// The filesystem that handles operations on this file
fs: *const FileSystem,
/// See the documentation for FileSystem.Read
pub fn read(self: *const FileNode, len: usize) (Allocator.Error || Error)![]u8 {
return self.fs.read(self.fs, self, len);
}
/// See the documentation for FileSystem.Close
pub fn close(self: *const FileNode) void {
return self.fs.close(self.fs, self);
}
/// See the documentation for FileSystem.Write
pub fn write(self: *const FileNode, bytes: []const u8) (Allocator.Error || Error)!void {
return self.fs.write(self.fs, self, bytes);
}
};
/// A node representing a directory within a filesystem
pub const DirNode = struct {
/// The filesystem that handles operations on this directory
fs: *const FileSystem,
/// The directory that this directory is mounted to, else null
mount: ?*const DirNode,
/// See the documentation for FileSystem.Open
pub fn open(self: *const DirNode, name: []const u8, flags: OpenFlags) (Allocator.Error || Error)!*Node {
var fs = self.fs;
var node = self;
if (self.mount) |mnt| {
fs = mnt.fs;
node = mnt;
}
return fs.open(fs, node, name, flags);
}
};
/// Errors that can be thrown by filesystem functions
pub const Error = error{
/// The file or directory requested doesn't exist in the filesystem
NoSuchFileOrDir,
/// The parent of a requested file or directory isn't a directory itself
NotADirectory,
/// The requested file is actually a directory
IsADirectory,
/// The path provided is not absolute
NotAbsolutePath,
/// 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
pub const MountError = error{
/// The directory being mounted to a filesystem is already mounted to something
DirAlreadyMounted,
};
/// The separator used between segments of a file path
pub const SEPARATOR: u8 = '/';
/// The root of the system's top-level filesystem
var root: *Node = undefined;
///
/// Traverse the specified path from the root and open the file/dir corresponding to that path. If the file/dir doesn't exist it can be created by specifying the open flags
///
/// Arguments:
/// IN path: []const u8 - The path to traverse. Must be absolute (see isAbsolute)
/// IN flags: OpenFlags - The flags that specify if the file/dir should be created if it doesn't exist
///
/// Return: *const Node
/// The node that exists at the path starting at the system root
///
/// Error: Allocator.Error || Error
/// Allocator.Error.OutOfMemory - There wasn't enough memory to fulfill the request
/// Error.NotADirectory - A segment within the path which is not at the end does not correspond to a directory
/// Error.NoSuchFileOrDir - The file/dir at the end of the path doesn't exist and the flags didn't specify to create it
///
fn traversePath(path: []const u8, flags: OpenFlags) (Allocator.Error || Error)!*Node {
if (!isAbsolute(path)) {
return Error.NotAbsolutePath;
}
const TraversalParent = struct {
parent: *Node,
child: []const u8,
const Self = @This();
fn func(split: *std.mem.SplitIterator, node: *Node, rec_flags: OpenFlags) (Allocator.Error || Error)!Self {
// Get segment string. This will not be unreachable as we've made sure the spliterator has more segments left
const seg = split.next() orelse unreachable;
if (split.rest().len == 0) {
return Self{
.parent = node,
.child = seg,
};
}
return switch (node.*) {
.File => Error.NotADirectory,
.Dir => |*dir| try func(split, try dir.open(seg, rec_flags), rec_flags),
};
}
};
// Split path but skip the first separator character
var split = std.mem.split(path[1..], &[_]u8{SEPARATOR});
// Traverse directories while we're not at the last segment
const result = try TraversalParent.func(&split, root, .NO_CREATION);
// There won't always be a second segment in the path, e.g. in "/"
if (std.mem.eql(u8, result.child, "")) {
return result.parent;
}
// Open the final segment of the path from whatever the parent is
return switch (result.parent.*) {
.File => Error.NotADirectory,
.Dir => |*dir| try dir.open(result.child, flags),
};
}
///
/// Mount the root of a filesystem to a directory. Opening files within that directory will then redirect to the target filesystem
///
/// Arguments:
/// IN dir: *DirNode - The directory to mount to. dir.mount is modified.
/// IN fs: *const FileSystem - The filesystem to mount
///
/// Error: MountError
/// MountError.DirAlreadyMounted - The directory is already mounted to a filesystem
///
pub fn mount(dir: *DirNode, fs: *const FileSystem) MountError!void {
if (dir.mount) |_| {
return MountError.DirAlreadyMounted;
}
dir.mount = fs.getRootNode(fs);
}
///
/// Open a node at a path.
///
/// Arguments:
/// IN path: []const u8 - The path to open. Must be absolute (see isAbsolute)
/// IN flags: OpenFlags - The flags specifying if this node should be created if it doesn't exist
///
/// Return: *const Node
/// The node that exists at the path starting at the system root
///
/// Error: Allocator.Error || Error
/// Allocator.Error.OutOfMemory - There wasn't enough memory to fulfill the request
/// Error.NotADirectory - A segment within the path which is not at the end does not correspond to a directory
/// Error.NoSuchFileOrDir - The file/dir at the end of the path doesn't exist and the flags didn't specify to create it
///
pub fn open(path: []const u8, flags: OpenFlags) (Allocator.Error || Error)!*Node {
return try traversePath(path, flags);
}
///
/// Open a file at a path.
///
/// Arguments:
/// IN path: []const u8 - The path to open. Must be absolute (see isAbsolute)
/// IN flags: OpenFlags - The flags specifying if this node should be created if it doesn't exist. Cannot be CREATE_DIR
///
/// Return: *const FileNode
/// The node that exists at the path starting at the system root
///
/// Error: Allocator.Error || Error
/// Allocator.Error.OutOfMemory - There wasn't enough memory to fulfill the request
/// Error.InvalidFlags - The flags were a value invalid when opening files
/// Error.NotADirectory - A segment within the path which is not at the end does not correspond to a directory
/// Error.NoSuchFileOrDir - The file/dir at the end of the path doesn't exist and the flags didn't specify to create it
/// Error.IsADirectory - The path corresponds to a directory rather than a file
///
pub fn openFile(path: []const u8, flags: OpenFlags) (Allocator.Error || Error)!*const FileNode {
switch (flags) {
.CREATE_DIR => return Error.InvalidFlags,
.NO_CREATION, .CREATE_FILE => {},
}
var node = try open(path, flags);
return switch (node.*) {
.File => &node.File,
.Dir => Error.IsADirectory,
};
}
///
/// Open a directory at a path.
///
/// Arguments:
/// IN path: []const u8 - The path to open. Must be absolute (see isAbsolute)
/// IN flags: OpenFlags - The flags specifying if this node should be created if it doesn't exist. Cannot be CREATE_FILE
///
/// Return: *const DirNode
/// The node that exists at the path starting at the system root
///
/// Error: Allocator.Error || Error
/// Allocator.Error.OutOfMemory - There wasn't enough memory to fulfill the request
/// Error.InvalidFlags - The flags were a value invalid when opening files
/// Error.NotADirectory - A segment within the path which is not at the end does not correspond to a directory
/// Error.NoSuchFileOrDir - The file/dir at the end of the path doesn't exist and the flags didn't specify to create it
/// Error.NotADirectory - The path corresponds to a file rather than a directory
///
pub fn openDir(path: []const u8, flags: OpenFlags) (Allocator.Error || Error)!*DirNode {
switch (flags) {
.CREATE_FILE => return Error.InvalidFlags,
.NO_CREATION, .CREATE_DIR => {},
}
var node = try open(path, flags);
return switch (node.*) {
.File => Error.NotADirectory,
.Dir => &node.Dir,
};
}
// TODO: Replace this with the std lib implementation once the OS abstraction layer is up and running
///
/// Check if a path is absolute, i.e. its length is greater than 0 and starts with the path separator character
///
/// Arguments:
/// IN path: []const u8 - The path to check
///
/// Return: bool
/// True if the path is absolute else false
///
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,
name: []u8,
data: ?[]u8,
children: *ArrayList(*@This()),
fn deinit(self: *@This(), allocator: *Allocator) void {
allocator.destroy(self.val);
allocator.free(self.name);
if (self.data) |d| {
allocator.free(d);
}
for (self.children.items) |child| {
child.deinit(allocator);
allocator.destroy(child);
}
self.children.deinit();
allocator.destroy(self.children);
}
};
tree: TreeNode,
fs: *FileSystem,
allocator: *Allocator,
instance: usize,
const Self = @This();
fn deinit(self: *@This()) void {
self.tree.deinit(self.allocator);
self.allocator.destroy(self.fs);
}
fn getTreeNode(test_fs: *Self, node: anytype) Allocator.Error!?*TreeNode {
switch (@TypeOf(node)) {
*const Node, *const FileNode, *const DirNode => {},
else => @compileError("Node is of type " ++ @typeName(@TypeOf(node)) ++ ". Only *const Node, *const FileNode and *const DirNode are supported"),
}
// Form a list containing all directory nodes to check via a breadth-first search
// This is inefficient but good for testing as it's clear and easy to modify
var to_check = TailQueue(*TreeNode).init();
var root_node = try to_check.createNode(&test_fs.tree, test_fs.allocator);
to_check.append(root_node);
while (to_check.popFirst()) |queue_node| {
var tree_node = queue_node.data;
to_check.destroyNode(queue_node, test_fs.allocator);
if ((@TypeOf(node) == *const FileNode and tree_node.val.isFile() and &tree_node.val.File == node) or (@TypeOf(node) == *const DirNode and tree_node.val.isDir() and &tree_node.val.Dir == node) or (@TypeOf(node) == *const Node and &tree_node.val == node)) {
// Clean up any unused queue nodes
while (to_check.popFirst()) |t_node| {
to_check.destroyNode(t_node, test_fs.allocator);
}
return tree_node;
}
for (tree_node.children.items) |child| {
// It's not the parent so add its children to the list for checking
to_check.append(try to_check.createNode(child, test_fs.allocator));
}
}
return null;
}
fn getRootNode(fs: *const FileSystem) *const DirNode {
var test_fs = @fieldParentPtr(TestFS, "instance", fs.instance);
return &test_fs.tree.val.Dir;
}
fn close(fs: *const FileSystem, node: *const FileNode) void {}
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;
const count = if (tree.data) |d| std.math.min(len, d.len) else 0;
const data = if (tree.data) |d| d[0..count] else "";
var bytes = try test_fs.allocator.alloc(u8, count);
std.mem.copy(u8, bytes, data);
return bytes;
}
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) |_| {
test_fs.allocator.free(tree.data.?);
}
tree.data = try test_fs.allocator.alloc(u8, bytes.len);
std.mem.copy(u8, tree.data.?, bytes);
}
fn open(fs: *const FileSystem, dir: *const DirNode, name: []const u8, flags: OpenFlags) (Allocator.Error || Error)!*Node {
var test_fs = @fieldParentPtr(TestFS, "instance", fs.instance);
const parent = (try getTreeNode(test_fs, dir)) orelse unreachable;
// Check if the children match the file wanted
for (parent.children.items) |child| {
if (std.mem.eql(u8, child.name, name)) {
return child.val;
}
}
// The file/dir doesn't exist so create it if necessary
if (flags != .NO_CREATION) {
var child: *Node = undefined;
switch (flags) {
.CREATE_DIR => {
// Create the fs node
child = try test_fs.allocator.create(Node);
child.* = .{ .Dir = .{ .fs = test_fs.fs, .mount = null } };
},
.CREATE_FILE => {
// Create the fs node
child = try test_fs.allocator.create(Node);
child.* = .{ .File = .{ .fs = test_fs.fs } };
},
.NO_CREATION => unreachable,
}
// Create the test fs tree node
var child_tree = try test_fs.allocator.create(TreeNode);
var child_name = try test_fs.allocator.alloc(u8, name.len);
std.mem.copy(u8, child_name, name);
child_tree.* = .{
.val = child,
.name = child_name,
.children = try test_fs.allocator.create(ArrayList(*TreeNode)),
.data = null,
};
child_tree.children.* = ArrayList(*TreeNode).init(test_fs.allocator);
// Add it to the tree
try parent.children.append(child_tree);
return child;
}
return Error.NoSuchFileOrDir;
}
};
fn testInitFs(allocator: *Allocator) !*TestFS {
const fs = try allocator.create(FileSystem);
var testfs = try allocator.create(TestFS);
var root_node = try allocator.create(Node);
root_node.* = .{ .Dir = .{ .fs = fs, .mount = null } };
var name = try allocator.alloc(u8, 4);
std.mem.copy(u8, name, "root");
testfs.* = TestFS{
.tree = .{
.val = root_node,
.name = name,
.children = try allocator.create(ArrayList(*TestFS.TreeNode)),
.data = null,
},
.fs = fs,
.instance = 123,
.allocator = allocator,
};
testfs.tree.children.* = ArrayList(*TestFS.TreeNode).init(allocator);
fs.* = .{ .open = TestFS.open, .close = TestFS.close, .read = TestFS.read, .write = TestFS.write, .instance = &testfs.instance, .getRootNode = TestFS.getRootNode };
return testfs;
}
test "mount" {
var allocator = testing.allocator;
// The root fs
var testfs = try testInitFs(allocator);
defer testfs.deinit();
defer allocator.destroy(testfs);
testfs.instance = 1;
root = testfs.tree.val;
// The fs that is to be mounted
var testfs2 = try testInitFs(allocator);
defer testfs2.deinit();
defer allocator.destroy(testfs2);
testfs2.instance = 2;
// Create the dir to mount to
var dir = try openDir("/mnt", .CREATE_DIR);
try mount(dir, testfs2.fs);
testing.expectError(MountError.DirAlreadyMounted, mount(dir, testfs2.fs));
// Ensure the mount worked
testing.expectEqual((dir.mount orelse unreachable), testfs2.fs.getRootNode(testfs2.fs));
testing.expectEqual((dir.mount orelse unreachable).fs, testfs2.fs);
// Create a file within the mounted directory
var test_file = try openFile("/mnt/123.txt", .CREATE_FILE);
testing.expectEqual(@ptrCast(*const FileSystem, testfs2.fs), test_file.fs);
// This shouldn't be in the root fs
testing.expectEqual(@as(usize, 1), testfs.tree.children.items.len);
testing.expectEqual(@as(usize, 0), testfs.tree.children.items[0].children.items.len);
// It should be in the mounted fs
testing.expectEqual(@as(usize, 1), testfs2.tree.children.items.len);
testing.expectEqual(test_file, &testfs2.tree.children.items[0].val.File);
}
test "traversePath" {
var allocator = testing.allocator;
var testfs = try testInitFs(allocator);
defer testfs.deinit();
defer allocator.destroy(testfs);
root = testfs.tree.val;
// Get the root
var test_root = try traversePath("/", .NO_CREATION);
testing.expectEqual(test_root, root);
// Create a file in the root and try to traverse to it
var child1 = try test_root.Dir.open("child1.txt", .CREATE_FILE);
testing.expectEqual(child1, try traversePath("/child1.txt", .NO_CREATION));
// Same but with a directory
var child2 = try test_root.Dir.open("child2", .CREATE_DIR);
testing.expectEqual(child2, try traversePath("/child2", .NO_CREATION));
// Again but with a file within that directory
var child3 = try child2.Dir.open("child3.txt", .CREATE_FILE);
testing.expectEqual(child3, try traversePath("/child2/child3.txt", .NO_CREATION));
testing.expectError(Error.NotAbsolutePath, traversePath("abc", .NO_CREATION));
testing.expectError(Error.NotAbsolutePath, traversePath("", .NO_CREATION));
testing.expectError(Error.NotAbsolutePath, traversePath("a/", .NO_CREATION));
testing.expectError(Error.NoSuchFileOrDir, traversePath("/notadir/abc.txt", .NO_CREATION));
testing.expectError(Error.NoSuchFileOrDir, traversePath("/ ", .NO_CREATION));
testing.expectError(Error.NotADirectory, traversePath("/child1.txt/abc.txt", .NO_CREATION));
}
test "isAbsolute" {
testing.expect(isAbsolute("/"));
testing.expect(isAbsolute("/abc"));
testing.expect(isAbsolute("/abc/def"));
testing.expect(isAbsolute("/ a bc/de f"));
testing.expect(isAbsolute("//"));
testing.expect(!isAbsolute(" /"));
testing.expect(!isAbsolute(""));
testing.expect(!isAbsolute("abc"));
testing.expect(!isAbsolute("abc/def"));
}
test "isDir" {
const fs: FileSystem = undefined;
const dir = Node{ .Dir = .{ .fs = &fs, .mount = null } };
const file = Node{ .File = .{ .fs = &fs } };
testing.expect(dir.isDir());
testing.expect(!file.isDir());
}
test "isFile" {
const fs: FileSystem = undefined;
const dir = Node{ .Dir = .{ .fs = &fs, .mount = null } };
const file = Node{ .File = .{ .fs = &fs } };
testing.expect(!dir.isFile());
testing.expect(file.isFile());
}
test "open" {
var testfs = try testInitFs(testing.allocator);
defer testfs.deinit();
defer testing.allocator.destroy(testfs);
root = testfs.tree.val;
// Creating a file
var test_node = try openFile("/abc.txt", .CREATE_FILE);
testing.expectEqual(testfs.tree.children.items.len, 1);
var tree = testfs.tree.children.items[0];
testing.expect(tree.val.isFile());
testing.expectEqual(test_node, &tree.val.File);
testing.expect(std.mem.eql(u8, tree.name, "abc.txt"));
testing.expectEqual(tree.data, null);
testing.expectEqual(tree.children.items.len, 0);
// Creating a dir
var test_dir = try openDir("/def", .CREATE_DIR);
testing.expectEqual(testfs.tree.children.items.len, 2);
tree = testfs.tree.children.items[1];
testing.expect(tree.val.isDir());
testing.expectEqual(test_dir, &tree.val.Dir);
testing.expect(std.mem.eql(u8, tree.name, "def"));
testing.expectEqual(tree.data, null);
testing.expectEqual(tree.children.items.len, 0);
// Creating a file under a new dir
test_node = try openFile("/def/ghi.zig", .CREATE_FILE);
testing.expectEqual(testfs.tree.children.items[1].children.items.len, 1);
tree = testfs.tree.children.items[1].children.items[0];
testing.expect(tree.val.isFile());
testing.expectEqual(test_node, &tree.val.File);
testing.expect(std.mem.eql(u8, tree.name, "ghi.zig"));
testing.expectEqual(tree.data, null);
testing.expectEqual(tree.children.items.len, 0);
testing.expectError(Error.NoSuchFileOrDir, openDir("/jkl", .NO_CREATION));
testing.expectError(Error.NoSuchFileOrDir, openFile("/mno.txt", .NO_CREATION));
testing.expectError(Error.NoSuchFileOrDir, openFile("/def/pqr.txt", .NO_CREATION));
testing.expectError(Error.NoSuchFileOrDir, openDir("/mno/stu", .NO_CREATION));
testing.expectError(Error.NoSuchFileOrDir, openFile("/mno/stu.txt", .NO_CREATION));
testing.expectError(Error.NotADirectory, openFile("/abc.txt/vxy.md", .NO_CREATION));
testing.expectError(Error.IsADirectory, openFile("/def", .NO_CREATION));
testing.expectError(Error.InvalidFlags, openFile("/abc.txt", .CREATE_DIR));
testing.expectError(Error.InvalidFlags, openDir("/abc.txt", .CREATE_FILE));
testing.expectError(Error.NotAbsolutePath, open("", .NO_CREATION));
testing.expectError(Error.NotAbsolutePath, open("abc", .NO_CREATION));
}
test "read" {
var testfs = try testInitFs(testing.allocator);
defer testfs.deinit();
defer testing.allocator.destroy(testfs);
root = testfs.tree.val;
var test_file = try openFile("/foo.txt", .CREATE_FILE);
var f_data = &testfs.tree.children.items[0].data;
var str = "test123";
f_data.* = try std.mem.dupe(testing.allocator, u8, str);
{
var data = try test_file.read(str.len);
defer testing.allocator.free(data);
testing.expect(std.mem.eql(u8, str, data));
}
{
var data = try test_file.read(str.len + 1);
defer testing.allocator.free(data);
testing.expect(std.mem.eql(u8, str, data));
}
{
var data = try test_file.read(str.len + 3);
defer testing.allocator.free(data);
testing.expect(std.mem.eql(u8, str, data));
}
{
var data = try test_file.read(str.len - 1);
defer testing.allocator.free(data);
testing.expect(std.mem.eql(u8, str[0 .. str.len - 1], data));
}
testing.expect(std.mem.eql(u8, str[0..0], try test_file.read(0)));
}
test "write" {
var testfs = try testInitFs(testing.allocator);
defer testfs.deinit();
defer testing.allocator.destroy(testfs);
root = testfs.tree.val;
var test_file = try openFile("/foo.txt", .CREATE_FILE);
var f_data = &testfs.tree.children.items[0].data;
testing.expectEqual(f_data.*, null);
var str = "test123";
try test_file.write(str);
testing.expect(std.mem.eql(u8, str, f_data.* orelse unreachable));
}