commit
374e95f322
2 changed files with 190 additions and 39 deletions
|
@ -90,10 +90,10 @@ pub const InitrdFS = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See vfs.FileSystem.open
|
/// 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 {
|
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);
|
var self = @fieldParentPtr(InitrdFS, "instance", fs.instance);
|
||||||
switch (flags) {
|
switch (flags) {
|
||||||
.CREATE_DIR, .CREATE_FILE => return vfs.Error.InvalidFlags,
|
.CREATE_DIR, .CREATE_FILE, .CREATE_SYMLINK => return vfs.Error.InvalidFlags,
|
||||||
.NO_CREATION => {
|
.NO_CREATION => {
|
||||||
for (self.files) |*file| {
|
for (self.files) |*file| {
|
||||||
if (std.mem.eql(u8, file.name, name)) {
|
if (std.mem.eql(u8, file.name, name)) {
|
||||||
|
@ -380,7 +380,7 @@ test "open valid file" {
|
||||||
expectEqual(fs.opened_files.count(), 1);
|
expectEqual(fs.opened_files.count(), 1);
|
||||||
expectEqualSlices(u8, fs.opened_files.get(file1_node).?.name, "test1.txt");
|
expectEqualSlices(u8, fs.opened_files.get(file1_node).?.name, "test1.txt");
|
||||||
|
|
||||||
var file3_node = try vfs.open("/test3.txt", .NO_CREATION);
|
var file3_node = try vfs.open("/test3.txt", true, .NO_CREATION, .{});
|
||||||
defer file3_node.File.close();
|
defer file3_node.File.close();
|
||||||
|
|
||||||
expectEqual(fs.opened_files.count(), 2);
|
expectEqual(fs.opened_files.count(), 2);
|
||||||
|
@ -388,7 +388,7 @@ test "open valid file" {
|
||||||
|
|
||||||
var dir1 = try vfs.openDir("/", .NO_CREATION);
|
var dir1 = try vfs.openDir("/", .NO_CREATION);
|
||||||
expectEqual(&fs.root_node.Dir, dir1);
|
expectEqual(&fs.root_node.Dir, dir1);
|
||||||
var file2 = &(try dir1.open("test2.txt", .NO_CREATION)).File;
|
var file2 = &(try dir1.open("test2.txt", .NO_CREATION, .{})).File;
|
||||||
defer file2.close();
|
defer file2.close();
|
||||||
|
|
||||||
expectEqual(fs.opened_files.count(), 3);
|
expectEqual(fs.opened_files.count(), 3);
|
||||||
|
@ -406,12 +406,19 @@ test "open fail with invalid flags" {
|
||||||
|
|
||||||
expectError(error.InvalidFlags, vfs.openFile("/text10.txt", .CREATE_DIR));
|
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_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_DIR));
|
||||||
expectError(error.InvalidFlags, vfs.openDir("/text10.txt", .CREATE_FILE));
|
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_DIR));
|
||||||
expectError(error.InvalidFlags, vfs.openFile("/test/", .CREATE_FILE));
|
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_DIR));
|
||||||
expectError(error.InvalidFlags, vfs.openDir("/test/", .CREATE_FILE));
|
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" {
|
test "open fail with NoSuchFileOrDir" {
|
||||||
|
@ -484,7 +491,7 @@ test "close a file" {
|
||||||
|
|
||||||
expectEqual(fs.opened_files.count(), 1);
|
expectEqual(fs.opened_files.count(), 1);
|
||||||
|
|
||||||
var file3_node = try vfs.open("/test3.txt", .NO_CREATION);
|
var file3_node = try vfs.open("/test3.txt", true, .NO_CREATION, .{});
|
||||||
|
|
||||||
expectEqual(fs.opened_files.count(), 2);
|
expectEqual(fs.opened_files.count(), 2);
|
||||||
file1.close();
|
file1.close();
|
||||||
|
@ -492,7 +499,7 @@ test "close a file" {
|
||||||
|
|
||||||
var dir1 = try vfs.openDir("/", .NO_CREATION);
|
var dir1 = try vfs.openDir("/", .NO_CREATION);
|
||||||
expectEqual(&fs.root_node.Dir, dir1);
|
expectEqual(&fs.root_node.Dir, dir1);
|
||||||
var file2 = &(try dir1.open("test2.txt", .NO_CREATION)).File;
|
var file2 = &(try dir1.open("test2.txt", .NO_CREATION, .{})).File;
|
||||||
defer file2.close();
|
defer file2.close();
|
||||||
|
|
||||||
expectEqual(fs.opened_files.count(), 2);
|
expectEqual(fs.opened_files.count(), 2);
|
||||||
|
|
|
@ -10,16 +10,27 @@ pub const OpenFlags = enum {
|
||||||
CREATE_DIR,
|
CREATE_DIR,
|
||||||
/// Create a file if it doesn't exist
|
/// Create a file if it doesn't exist
|
||||||
CREATE_FILE,
|
CREATE_FILE,
|
||||||
|
/// Create a symlink if it doesn't exist
|
||||||
|
CREATE_SYMLINK,
|
||||||
/// Do not create a file or directory
|
/// Do not create a file or directory
|
||||||
NO_CREATION,
|
NO_CREATION,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// The args used when opening new or existing fs nodes
|
||||||
|
pub const OpenArgs = struct {
|
||||||
|
/// What to set the target to when creating a symlink
|
||||||
|
symlink_target: ?[]const u8 = null,
|
||||||
|
};
|
||||||
|
|
||||||
/// A filesystem node that could either be a directory or a file
|
/// A filesystem node that could either be a directory or a file
|
||||||
pub const Node = union(enum) {
|
pub const Node = union(enum) {
|
||||||
/// The file node if this represents a file
|
/// The file node if this represents a file
|
||||||
File: FileNode,
|
File: FileNode,
|
||||||
/// The dir node if this represents a directory
|
/// The dir node if this represents a directory
|
||||||
Dir: DirNode,
|
Dir: DirNode,
|
||||||
|
/// The absolute path that this symlink is linked to
|
||||||
|
Symlink: []const u8,
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
///
|
///
|
||||||
|
@ -34,7 +45,7 @@ pub const Node = union(enum) {
|
||||||
pub fn isDir(self: Self) bool {
|
pub fn isDir(self: Self) bool {
|
||||||
return switch (self) {
|
return switch (self) {
|
||||||
.Dir => true,
|
.Dir => true,
|
||||||
.File => false,
|
else => false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +61,23 @@ pub const Node = union(enum) {
|
||||||
pub fn isFile(self: Self) bool {
|
pub fn isFile(self: Self) bool {
|
||||||
return switch (self) {
|
return switch (self) {
|
||||||
.File => true,
|
.File => true,
|
||||||
.Dir => false,
|
else => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Check if this node is a symlink
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// IN self: Self - The node being checked
|
||||||
|
///
|
||||||
|
/// Return: bool
|
||||||
|
/// True if this is a symlink else false
|
||||||
|
///
|
||||||
|
pub fn isSymlink(self: Self) bool {
|
||||||
|
return switch (self) {
|
||||||
|
.Symlink => true,
|
||||||
|
else => false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -109,6 +136,7 @@ pub const FileSystem = struct {
|
||||||
/// IN node: *const DirNode - The directory under which to open the file/dir from
|
/// 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 name: []const u8 - The name of the file to open
|
||||||
/// IN flags: OpenFlags - The flags to consult when opening the file
|
/// IN flags: OpenFlags - The flags to consult when opening the file
|
||||||
|
/// IN args: OpenArgs - The arguments to use when creating a node
|
||||||
///
|
///
|
||||||
/// Return: *const Node
|
/// Return: *const Node
|
||||||
/// The node representing the file/dir opened
|
/// The node representing the file/dir opened
|
||||||
|
@ -116,8 +144,9 @@ pub const FileSystem = struct {
|
||||||
/// Error: Allocator.Error || Error
|
/// Error: Allocator.Error || Error
|
||||||
/// Allocator.Error.OutOfMemory - There wasn't enough memory to fulfill the request
|
/// 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
|
/// Error.NoSuchFileOrDir - The file/dir by that name doesn't exist and the flags didn't specify to create it
|
||||||
|
/// Error.NoSymlinkTarget - A symlink was created but no symlink target was provided in the args
|
||||||
///
|
///
|
||||||
const Open = fn (self: *const Self, node: *const DirNode, name: []const u8, flags: OpenFlags) (Allocator.Error || Error)!*Node;
|
const Open = fn (self: *const Self, node: *const DirNode, name: []const u8, flags: OpenFlags, args: OpenArgs) (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
|
/// Get the node representing the root of the filesystem. Used when mounting to bind the mount point to the root of the mounted fs
|
||||||
|
@ -180,14 +209,14 @@ pub const DirNode = struct {
|
||||||
mount: ?*const DirNode,
|
mount: ?*const DirNode,
|
||||||
|
|
||||||
/// See the documentation for FileSystem.Open
|
/// See the documentation for FileSystem.Open
|
||||||
pub fn open(self: *const DirNode, name: []const u8, flags: OpenFlags) (Allocator.Error || Error)!*Node {
|
pub fn open(self: *const DirNode, name: []const u8, flags: OpenFlags, args: OpenArgs) (Allocator.Error || Error)!*Node {
|
||||||
var fs = self.fs;
|
var fs = self.fs;
|
||||||
var node = self;
|
var node = self;
|
||||||
if (self.mount) |mnt| {
|
if (self.mount) |mnt| {
|
||||||
fs = mnt.fs;
|
fs = mnt.fs;
|
||||||
node = mnt;
|
node = mnt;
|
||||||
}
|
}
|
||||||
return fs.open(fs, node, name, flags);
|
return fs.open(fs, node, name, flags, args);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -202,6 +231,9 @@ pub const Error = error{
|
||||||
/// The requested file is actually a directory
|
/// The requested file is actually a directory
|
||||||
IsADirectory,
|
IsADirectory,
|
||||||
|
|
||||||
|
/// The requested symlink is actually a file
|
||||||
|
IsAFile,
|
||||||
|
|
||||||
/// The path provided is not absolute
|
/// The path provided is not absolute
|
||||||
NotAbsolutePath,
|
NotAbsolutePath,
|
||||||
|
|
||||||
|
@ -210,6 +242,9 @@ pub const Error = error{
|
||||||
|
|
||||||
/// The node is not recognised as being opened by the filesystem
|
/// The node is not recognised as being opened by the filesystem
|
||||||
NotOpened,
|
NotOpened,
|
||||||
|
|
||||||
|
/// No symlink target was provided when one was expected
|
||||||
|
NoSymlinkTarget,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Errors that can be thrown when attempting to mount
|
/// Errors that can be thrown when attempting to mount
|
||||||
|
@ -230,6 +265,7 @@ var root: *Node = undefined;
|
||||||
/// Arguments:
|
/// Arguments:
|
||||||
/// IN path: []const u8 - The path to traverse. Must be absolute (see isAbsolute)
|
/// 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
|
/// IN flags: OpenFlags - The flags that specify if the file/dir should be created if it doesn't exist
|
||||||
|
/// IN args: OpenArgs - The extra args needed when creating new nodes.
|
||||||
///
|
///
|
||||||
/// Return: *const Node
|
/// Return: *const Node
|
||||||
/// The node that exists at the path starting at the system root
|
/// The node that exists at the path starting at the system root
|
||||||
|
@ -238,8 +274,9 @@ var root: *Node = undefined;
|
||||||
/// Allocator.Error.OutOfMemory - There wasn't enough memory to fulfill the request
|
/// 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.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.NoSuchFileOrDir - The file/dir at the end of the path doesn't exist and the flags didn't specify to create it
|
||||||
|
/// Error.NoSymlinkTarget - A non-null symlink target was not provided when creating a symlink
|
||||||
///
|
///
|
||||||
fn traversePath(path: []const u8, flags: OpenFlags) (Allocator.Error || Error)!*Node {
|
fn traversePath(path: []const u8, follow_symlinks: bool, flags: OpenFlags, args: OpenArgs) (Allocator.Error || Error)!*Node {
|
||||||
if (!isAbsolute(path)) {
|
if (!isAbsolute(path)) {
|
||||||
return Error.NotAbsolutePath;
|
return Error.NotAbsolutePath;
|
||||||
}
|
}
|
||||||
|
@ -250,7 +287,7 @@ fn traversePath(path: []const u8, flags: OpenFlags) (Allocator.Error || Error)!*
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
fn func(split: *std.mem.SplitIterator, node: *Node, rec_flags: OpenFlags) (Allocator.Error || Error)!Self {
|
fn func(split: *std.mem.SplitIterator, node: *Node, follow_links: bool, 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
|
// 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;
|
const seg = split.next() orelse unreachable;
|
||||||
if (split.rest().len == 0) {
|
if (split.rest().len == 0) {
|
||||||
|
@ -261,7 +298,15 @@ fn traversePath(path: []const u8, flags: OpenFlags) (Allocator.Error || Error)!*
|
||||||
}
|
}
|
||||||
return switch (node.*) {
|
return switch (node.*) {
|
||||||
.File => Error.NotADirectory,
|
.File => Error.NotADirectory,
|
||||||
.Dir => |*dir| try func(split, try dir.open(seg, rec_flags), rec_flags),
|
.Dir => |*dir| blk: {
|
||||||
|
var child = try dir.open(seg, rec_flags, .{});
|
||||||
|
// If the segment refers to a symlink, redirect to the node it represents instead
|
||||||
|
if (child.isSymlink() and follow_links) {
|
||||||
|
child = try traversePath(child.Symlink, follow_links, rec_flags, .{});
|
||||||
|
}
|
||||||
|
break :blk try func(split, child, follow_links, rec_flags);
|
||||||
|
},
|
||||||
|
.Symlink => |target| if (follow_links) try func(split, try traversePath(target, follow_links, .NO_CREATION, .{}), follow_links, rec_flags) else Error.NotADirectory,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -269,7 +314,7 @@ fn traversePath(path: []const u8, flags: OpenFlags) (Allocator.Error || Error)!*
|
||||||
// Split path but skip the first separator character
|
// Split path but skip the first separator character
|
||||||
var split = std.mem.split(path[1..], &[_]u8{SEPARATOR});
|
var split = std.mem.split(path[1..], &[_]u8{SEPARATOR});
|
||||||
// Traverse directories while we're not at the last segment
|
// Traverse directories while we're not at the last segment
|
||||||
const result = try TraversalParent.func(&split, root, .NO_CREATION);
|
const result = try TraversalParent.func(&split, root, follow_symlinks, .NO_CREATION);
|
||||||
|
|
||||||
// There won't always be a second segment in the path, e.g. in "/"
|
// There won't always be a second segment in the path, e.g. in "/"
|
||||||
if (std.mem.eql(u8, result.child, "")) {
|
if (std.mem.eql(u8, result.child, "")) {
|
||||||
|
@ -282,7 +327,15 @@ fn traversePath(path: []const u8, flags: OpenFlags) (Allocator.Error || Error)!*
|
||||||
file.close();
|
file.close();
|
||||||
break :blk Error.NotADirectory;
|
break :blk Error.NotADirectory;
|
||||||
},
|
},
|
||||||
.Dir => |*dir| try dir.open(result.child, flags),
|
.Symlink => |target| if (follow_symlinks) try traversePath(target, follow_symlinks, .NO_CREATION, .{}) else result.parent,
|
||||||
|
.Dir => |*dir| blk: {
|
||||||
|
var n = try dir.open(result.child, flags, args);
|
||||||
|
if (n.isSymlink() and follow_symlinks) {
|
||||||
|
// If the child is a symnlink and we're following them, find the node it refers to
|
||||||
|
n = try traversePath(n.Symlink, follow_symlinks, flags, args);
|
||||||
|
}
|
||||||
|
break :blk n;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,7 +361,9 @@ pub fn mount(dir: *DirNode, fs: *const FileSystem) MountError!void {
|
||||||
///
|
///
|
||||||
/// Arguments:
|
/// Arguments:
|
||||||
/// IN path: []const u8 - The path to open. Must be absolute (see isAbsolute)
|
/// IN path: []const u8 - The path to open. Must be absolute (see isAbsolute)
|
||||||
|
/// IN follow_symlinks: bool - Whether symbolic links should be followed. When this is false and the path traversal encounters a symlink before the end segment of the path, NotADirectory is returned.
|
||||||
/// IN flags: OpenFlags - The flags specifying if this node should be created if it doesn't exist
|
/// IN flags: OpenFlags - The flags specifying if this node should be created if it doesn't exist
|
||||||
|
/// IN args: OpenArgs - The extra args needed when creating new nodes.
|
||||||
///
|
///
|
||||||
/// Return: *const Node
|
/// Return: *const Node
|
||||||
/// The node that exists at the path starting at the system root
|
/// The node that exists at the path starting at the system root
|
||||||
|
@ -317,9 +372,10 @@ pub fn mount(dir: *DirNode, fs: *const FileSystem) MountError!void {
|
||||||
/// Allocator.Error.OutOfMemory - There wasn't enough memory to fulfill the request
|
/// 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.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.NoSuchFileOrDir - The file/dir at the end of the path doesn't exist and the flags didn't specify to create it
|
||||||
|
/// Error.NoSymlinkTarget - A non-null symlink target was not provided when creating a symlink
|
||||||
///
|
///
|
||||||
pub fn open(path: []const u8, flags: OpenFlags) (Allocator.Error || Error)!*Node {
|
pub fn open(path: []const u8, follow_symlinks: bool, flags: OpenFlags, args: OpenArgs) (Allocator.Error || Error)!*Node {
|
||||||
return try traversePath(path, flags);
|
return try traversePath(path, follow_symlinks, flags, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
|
@ -341,13 +397,15 @@ pub fn open(path: []const u8, flags: OpenFlags) (Allocator.Error || Error)!*Node
|
||||||
///
|
///
|
||||||
pub fn openFile(path: []const u8, flags: OpenFlags) (Allocator.Error || Error)!*const FileNode {
|
pub fn openFile(path: []const u8, flags: OpenFlags) (Allocator.Error || Error)!*const FileNode {
|
||||||
switch (flags) {
|
switch (flags) {
|
||||||
.CREATE_DIR => return Error.InvalidFlags,
|
.CREATE_DIR, .CREATE_SYMLINK => return Error.InvalidFlags,
|
||||||
.NO_CREATION, .CREATE_FILE => {},
|
.NO_CREATION, .CREATE_FILE => {},
|
||||||
}
|
}
|
||||||
var node = try open(path, flags);
|
var node = try open(path, true, flags, .{});
|
||||||
return switch (node.*) {
|
return switch (node.*) {
|
||||||
.File => &node.File,
|
.File => &node.File,
|
||||||
.Dir => Error.IsADirectory,
|
.Dir => Error.IsADirectory,
|
||||||
|
// We instructed open to folow symlinks above, so this is impossible
|
||||||
|
.Symlink => unreachable,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -370,19 +428,53 @@ pub fn openFile(path: []const u8, flags: OpenFlags) (Allocator.Error || Error)!*
|
||||||
///
|
///
|
||||||
pub fn openDir(path: []const u8, flags: OpenFlags) (Allocator.Error || Error)!*DirNode {
|
pub fn openDir(path: []const u8, flags: OpenFlags) (Allocator.Error || Error)!*DirNode {
|
||||||
switch (flags) {
|
switch (flags) {
|
||||||
.CREATE_FILE => return Error.InvalidFlags,
|
.CREATE_FILE, .CREATE_SYMLINK => return Error.InvalidFlags,
|
||||||
.NO_CREATION, .CREATE_DIR => {},
|
.NO_CREATION, .CREATE_DIR => {},
|
||||||
}
|
}
|
||||||
var node = try open(path, flags);
|
var node = try open(path, true, flags, .{});
|
||||||
return switch (node.*) {
|
return switch (node.*) {
|
||||||
.File => |*file| blk: {
|
.File => |*file| blk: {
|
||||||
file.close();
|
file.close();
|
||||||
break :blk Error.NotADirectory;
|
break :blk Error.NotADirectory;
|
||||||
},
|
},
|
||||||
|
// We instructed open to folow symlinks above, so this is impossible
|
||||||
|
.Symlink => unreachable,
|
||||||
.Dir => &node.Dir,
|
.Dir => &node.Dir,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Open a symlink at a path with a target.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// IN path: []const u8 - The path to open. Must be absolute (see isAbsolute)
|
||||||
|
/// IN tareget: ?[]const u8 - The target to use when creating the symlink. Can be null if .NO_CREATION is used as the open flag
|
||||||
|
/// IN flags: OpenFlags - The flags specifying if this node should be created if it doesn't exist. Cannot be CREATE_FILE
|
||||||
|
///
|
||||||
|
/// Return: []const u8
|
||||||
|
/// The opened symlink's target
|
||||||
|
///
|
||||||
|
/// 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 a symlink
|
||||||
|
/// Error.NotADirectory - A segment within the path which is not at the end does not correspond to a directory
|
||||||
|
/// Error.NoSuchFileOrDir - The symlink at the end of the path doesn't exist and the flags didn't specify to create it
|
||||||
|
/// Error.IsAFile - The path corresponds to a file rather than a symlink
|
||||||
|
/// Error.IsADirectory - The path corresponds to a directory rather than a symlink
|
||||||
|
///
|
||||||
|
pub fn openSymlink(path: []const u8, target: ?[]const u8, flags: OpenFlags) (Allocator.Error || Error)![]const u8 {
|
||||||
|
switch (flags) {
|
||||||
|
.CREATE_DIR, .CREATE_FILE => return Error.InvalidFlags,
|
||||||
|
.NO_CREATION, .CREATE_SYMLINK => {},
|
||||||
|
}
|
||||||
|
var node = try open(path, false, flags, .{ .symlink_target = target });
|
||||||
|
return switch (node.*) {
|
||||||
|
.Symlink => |t| t,
|
||||||
|
.File => Error.IsAFile,
|
||||||
|
.Dir => Error.IsADirectory,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Replace this with the std lib implementation once the OS abstraction layer is up and running
|
// 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
|
/// Check if a path is absolute, i.e. its length is greater than 0 and starts with the path separator character
|
||||||
|
@ -505,7 +597,7 @@ const TestFS = struct {
|
||||||
return bytes.len;
|
return bytes.len;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open(fs: *const FileSystem, dir: *const DirNode, name: []const u8, flags: OpenFlags) (Allocator.Error || Error)!*Node {
|
fn open(fs: *const FileSystem, dir: *const DirNode, name: []const u8, flags: OpenFlags, args: OpenArgs) (Allocator.Error || Error)!*Node {
|
||||||
var test_fs = @fieldParentPtr(TestFS, "instance", fs.instance);
|
var test_fs = @fieldParentPtr(TestFS, "instance", fs.instance);
|
||||||
const parent = (try getTreeNode(test_fs, dir)) orelse unreachable;
|
const parent = (try getTreeNode(test_fs, dir)) orelse unreachable;
|
||||||
// Check if the children match the file wanted
|
// Check if the children match the file wanted
|
||||||
|
@ -532,6 +624,14 @@ const TestFS = struct {
|
||||||
child = try test_fs.allocator.create(Node);
|
child = try test_fs.allocator.create(Node);
|
||||||
child.* = .{ .File = .{ .fs = test_fs.fs } };
|
child.* = .{ .File = .{ .fs = test_fs.fs } };
|
||||||
},
|
},
|
||||||
|
.CREATE_SYMLINK => {
|
||||||
|
if (args.symlink_target) |target| {
|
||||||
|
child = try test_fs.allocator.create(Node);
|
||||||
|
child.* = .{ .Symlink = target };
|
||||||
|
} else {
|
||||||
|
return Error.NoSymlinkTarget;
|
||||||
|
}
|
||||||
|
},
|
||||||
.NO_CREATION => unreachable,
|
.NO_CREATION => unreachable,
|
||||||
}
|
}
|
||||||
// Create the test fs tree node
|
// Create the test fs tree node
|
||||||
|
@ -625,34 +725,46 @@ test "traversePath" {
|
||||||
root = testfs.tree.val;
|
root = testfs.tree.val;
|
||||||
|
|
||||||
// Get the root
|
// Get the root
|
||||||
var test_root = try traversePath("/", .NO_CREATION);
|
var test_root = try traversePath("/", false, .NO_CREATION, .{});
|
||||||
testing.expectEqual(test_root, root);
|
testing.expectEqual(test_root, root);
|
||||||
// Create a file in the root and try to traverse to it
|
// Create a file in the root and try to traverse to it
|
||||||
var child1 = try test_root.Dir.open("child1.txt", .CREATE_FILE);
|
var child1 = try test_root.Dir.open("child1.txt", .CREATE_FILE, .{});
|
||||||
var child1_traversed = try traversePath("/child1.txt", .NO_CREATION);
|
var child1_traversed = try traversePath("/child1.txt", false, .NO_CREATION, .{});
|
||||||
testing.expectEqual(child1, child1_traversed);
|
testing.expectEqual(child1, child1_traversed);
|
||||||
// Close the open files
|
// Close the open files
|
||||||
child1.File.close();
|
child1.File.close();
|
||||||
child1_traversed.File.close();
|
child1_traversed.File.close();
|
||||||
|
|
||||||
// Same but with a directory
|
// Same but with a directory
|
||||||
var child2 = try test_root.Dir.open("child2", .CREATE_DIR);
|
var child2 = try test_root.Dir.open("child2", .CREATE_DIR, .{});
|
||||||
testing.expectEqual(child2, try traversePath("/child2", .NO_CREATION));
|
testing.expectEqual(child2, try traversePath("/child2", false, .NO_CREATION, .{}));
|
||||||
|
|
||||||
// Again but with a file within that directory
|
// Again but with a file within that directory
|
||||||
var child3 = try child2.Dir.open("child3.txt", .CREATE_FILE);
|
var child3 = try child2.Dir.open("child3.txt", .CREATE_FILE, .{});
|
||||||
var child3_traversed = try traversePath("/child2/child3.txt", .NO_CREATION);
|
var child3_traversed = try traversePath("/child2/child3.txt", false, .NO_CREATION, .{});
|
||||||
testing.expectEqual(child3, child3_traversed);
|
testing.expectEqual(child3, child3_traversed);
|
||||||
// Close the open files
|
// Close the open files
|
||||||
child3.File.close();
|
child3.File.close();
|
||||||
child3_traversed.File.close();
|
child3_traversed.File.close();
|
||||||
|
|
||||||
testing.expectError(Error.NotAbsolutePath, traversePath("abc", .NO_CREATION));
|
// Create and open a symlink
|
||||||
testing.expectError(Error.NotAbsolutePath, traversePath("", .NO_CREATION));
|
var child4 = try traversePath("/child2/link", false, .CREATE_SYMLINK, .{ .symlink_target = "/child2/child3.txt" });
|
||||||
testing.expectError(Error.NotAbsolutePath, traversePath("a/", .NO_CREATION));
|
var child4_linked = try traversePath("/child2/link", true, .NO_CREATION, .{});
|
||||||
testing.expectError(Error.NoSuchFileOrDir, traversePath("/notadir/abc.txt", .NO_CREATION));
|
testing.expectEqual(child4_linked, child3);
|
||||||
testing.expectError(Error.NoSuchFileOrDir, traversePath("/ ", .NO_CREATION));
|
var child5 = try traversePath("/child4", false, .CREATE_SYMLINK, .{ .symlink_target = "/child2" });
|
||||||
testing.expectError(Error.NotADirectory, traversePath("/child1.txt/abc.txt", .NO_CREATION));
|
var child5_linked = try traversePath("/child4/child3.txt", true, .NO_CREATION, .{});
|
||||||
|
std.debug.warn("child5_linked {}, child4_linked {}\n", .{ child5_linked, child4_linked });
|
||||||
|
testing.expectEqual(child5_linked, child4_linked);
|
||||||
|
child4_linked.File.close();
|
||||||
|
child5_linked.File.close();
|
||||||
|
|
||||||
|
testing.expectError(Error.NotAbsolutePath, traversePath("abc", false, .NO_CREATION, .{}));
|
||||||
|
testing.expectError(Error.NotAbsolutePath, traversePath("", false, .NO_CREATION, .{}));
|
||||||
|
testing.expectError(Error.NotAbsolutePath, traversePath("a/", false, .NO_CREATION, .{}));
|
||||||
|
testing.expectError(Error.NoSuchFileOrDir, traversePath("/notadir/abc.txt", false, .NO_CREATION, .{}));
|
||||||
|
testing.expectError(Error.NoSuchFileOrDir, traversePath("/ ", false, .NO_CREATION, .{}));
|
||||||
|
testing.expectError(Error.NotADirectory, traversePath("/child1.txt/abc.txt", false, .NO_CREATION, .{}));
|
||||||
|
testing.expectError(Error.NoSymlinkTarget, traversePath("/childX.txt", false, .CREATE_SYMLINK, .{}));
|
||||||
|
|
||||||
// Since we've closed all the files, the open files count should be zero
|
// Since we've closed all the files, the open files count should be zero
|
||||||
testing.expectEqual(testfs.open_files_count, 0);
|
testing.expectEqual(testfs.open_files_count, 0);
|
||||||
|
@ -674,18 +786,32 @@ test "isDir" {
|
||||||
const fs: FileSystem = undefined;
|
const fs: FileSystem = undefined;
|
||||||
const dir = Node{ .Dir = .{ .fs = &fs, .mount = null } };
|
const dir = Node{ .Dir = .{ .fs = &fs, .mount = null } };
|
||||||
const file = Node{ .File = .{ .fs = &fs } };
|
const file = Node{ .File = .{ .fs = &fs } };
|
||||||
testing.expect(dir.isDir());
|
const symlink = Node{ .Symlink = "" };
|
||||||
|
testing.expect(!symlink.isDir());
|
||||||
testing.expect(!file.isDir());
|
testing.expect(!file.isDir());
|
||||||
|
testing.expect(dir.isDir());
|
||||||
}
|
}
|
||||||
|
|
||||||
test "isFile" {
|
test "isFile" {
|
||||||
const fs: FileSystem = undefined;
|
const fs: FileSystem = undefined;
|
||||||
const dir = Node{ .Dir = .{ .fs = &fs, .mount = null } };
|
const dir = Node{ .Dir = .{ .fs = &fs, .mount = null } };
|
||||||
const file = Node{ .File = .{ .fs = &fs } };
|
const file = Node{ .File = .{ .fs = &fs } };
|
||||||
|
const symlink = Node{ .Symlink = "" };
|
||||||
testing.expect(!dir.isFile());
|
testing.expect(!dir.isFile());
|
||||||
|
testing.expect(!symlink.isFile());
|
||||||
testing.expect(file.isFile());
|
testing.expect(file.isFile());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "isSymlink" {
|
||||||
|
const fs: FileSystem = undefined;
|
||||||
|
const dir = Node{ .Dir = .{ .fs = &fs, .mount = null } };
|
||||||
|
const file = Node{ .File = .{ .fs = &fs } };
|
||||||
|
const symlink = Node{ .Symlink = "" };
|
||||||
|
testing.expect(!dir.isSymlink());
|
||||||
|
testing.expect(!file.isSymlink());
|
||||||
|
testing.expect(symlink.isSymlink());
|
||||||
|
}
|
||||||
|
|
||||||
test "open" {
|
test "open" {
|
||||||
var testfs = try testInitFs(testing.allocator);
|
var testfs = try testInitFs(testing.allocator);
|
||||||
defer testing.allocator.destroy(testfs);
|
defer testing.allocator.destroy(testfs);
|
||||||
|
@ -731,8 +857,9 @@ test "open" {
|
||||||
testing.expectError(Error.IsADirectory, openFile("/def", .NO_CREATION));
|
testing.expectError(Error.IsADirectory, openFile("/def", .NO_CREATION));
|
||||||
testing.expectError(Error.InvalidFlags, openFile("/abc.txt", .CREATE_DIR));
|
testing.expectError(Error.InvalidFlags, openFile("/abc.txt", .CREATE_DIR));
|
||||||
testing.expectError(Error.InvalidFlags, openDir("/abc.txt", .CREATE_FILE));
|
testing.expectError(Error.InvalidFlags, openDir("/abc.txt", .CREATE_FILE));
|
||||||
testing.expectError(Error.NotAbsolutePath, open("", .NO_CREATION));
|
testing.expectError(Error.NotAbsolutePath, open("", false, .NO_CREATION, .{}));
|
||||||
testing.expectError(Error.NotAbsolutePath, open("abc", .NO_CREATION));
|
testing.expectError(Error.NotAbsolutePath, open("abc", false, .NO_CREATION, .{}));
|
||||||
|
testing.expectError(Error.NoSymlinkTarget, open("/abc", false, .CREATE_SYMLINK, .{}));
|
||||||
}
|
}
|
||||||
|
|
||||||
test "read" {
|
test "read" {
|
||||||
|
@ -771,6 +898,14 @@ test "read" {
|
||||||
const length = try test_file.read(buffer[0..0]);
|
const length = try test_file.read(buffer[0..0]);
|
||||||
testing.expect(std.mem.eql(u8, str[0..0], buffer[0..length]));
|
testing.expect(std.mem.eql(u8, str[0..0], buffer[0..length]));
|
||||||
}
|
}
|
||||||
|
// Try reading from a symlink
|
||||||
|
var test_link = try openSymlink("/link", "/foo.txt", .CREATE_SYMLINK);
|
||||||
|
testing.expectEqual(test_link, "/foo.txt");
|
||||||
|
var link_file = try openFile("/link", .NO_CREATION);
|
||||||
|
{
|
||||||
|
const length = try link_file.read(buffer[0..0]);
|
||||||
|
testing.expect(std.mem.eql(u8, str[0..0], buffer[0..length]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test "write" {
|
test "write" {
|
||||||
|
@ -787,4 +922,13 @@ test "write" {
|
||||||
const length = try test_file.write(str);
|
const length = try test_file.write(str);
|
||||||
testing.expect(std.mem.eql(u8, str, f_data.* orelse unreachable));
|
testing.expect(std.mem.eql(u8, str, f_data.* orelse unreachable));
|
||||||
testing.expect(length == str.len);
|
testing.expect(length == str.len);
|
||||||
|
|
||||||
|
// Try writing to a symlink
|
||||||
|
var test_link = try openSymlink("/link", "/foo.txt", .CREATE_SYMLINK);
|
||||||
|
testing.expectEqual(test_link, "/foo.txt");
|
||||||
|
var link_file = try openFile("/link", .NO_CREATION);
|
||||||
|
|
||||||
|
var str2 = "test456";
|
||||||
|
const length2 = try test_file.write(str2);
|
||||||
|
testing.expect(std.mem.eql(u8, str2, f_data.* orelse unreachable));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue