First commit

This commit is contained in:
2026-05-21 21:31:17 +02:00
commit 6f14bec123
10 changed files with 2508 additions and 0 deletions
+416
View File
@@ -0,0 +1,416 @@
const std = @import("std");
const FileSystem = @This();
const Allocator = std.mem.Allocator;
const ENTITY_NAME_LIMIT = 127;
const MAX_PATH_COMPONENTS = 64;
const EntityChild = struct {
name: [ENTITY_NAME_LIMIT]u8 = undefined,
name_len: u16 = 0,
addr: *Entity,
fn init(name: []const u8, addr: *Entity) !EntityChild {
var self: EntityChild = .{ .addr = addr };
try self.setName(name);
return self;
}
fn setName(self: *EntityChild, name: []const u8) !void {
if (name.len > self.name.len)
return error.OutOfMemory;
@memcpy(self.name[0..name.len], name);
self.name_len = @intCast(name.len);
}
fn getName(self: *const EntityChild) []const u8 {
return self.name[0..self.name_len];
}
};
const Entity = struct {
is_dir : bool,
ref_count: u32 = 0,
children : std.ArrayList(EntityChild) = .empty,
bytes : std.ArrayList(u8) = .empty,
fn initDir(gpa: Allocator) Allocator.Error!*Entity {
const self = try gpa.create(Entity);
self.* = .{ .is_dir = true };
return self;
}
fn initFile(gpa: Allocator) Allocator.Error!*Entity {
const self = try gpa.create(Entity);
self.* = .{ .is_dir = false };
return self;
}
fn ref(self: *Entity) void {
self.ref_count += 1;
}
fn deref(self: *Entity, gpa: Allocator) void {
std.debug.assert(self.ref_count > 0);
self.ref_count -= 1;
if (self.ref_count == 0) {
for (self.children.items) |child| {
child.addr.deref(gpa);
}
self.children.deinit(gpa);
self.bytes.deinit(gpa);
gpa.destroy(self);
}
}
// Directory only
fn findChildIndex(self: *Entity, name: []const u8) ?usize {
std.debug.assert(self.is_dir);
for (self.children.items, 0..) |child, i| {
if (std.mem.eql(u8, child.getName(), name))
return i;
}
return null;
}
// Directory only
fn addChild(self: *Entity, gpa: Allocator, name: []const u8, addr: *Entity) !void {
std.debug.assert(self.is_dir);
if (self.findChildIndex(name) != null)
return error.ExistsAlready;
try self.children.append(gpa, try EntityChild.init(name, addr));
addr.ref();
}
// Directory only
fn findChild(self: *Entity, name: []const u8) ?*EntityChild {
const index = self.findChildIndex(name) orelse return null;
return &self.children.items[index];
}
// Directory only
fn removeChild(self: *Entity, gpa: Allocator, name: []const u8) !void {
const index = self.findChildIndex(name) orelse return error.NotFound;
const removed = self.children.swapRemove(index);
removed.addr.deref(gpa);
}
};
pub const OpenDir = struct {
entity: *Entity,
cursor: usize = 0,
name: [ENTITY_NAME_LIMIT]u8 = undefined,
fn init(entity: *Entity) OpenDir {
return .{ .entity = entity };
}
};
pub const OpenFile = struct {
entity: *Entity,
cursor: usize = 0,
fn init(entity: *Entity) OpenFile {
return .{ .entity = entity };
}
};
pub const ReadDir = struct {
name: []const u8,
is_dir: bool,
};
const ParsePathError = error{
EmptyPath,
NoRootParent,
TooManyComponents,
};
const ResolvePathError = error {
ResolutionLimit,
ComponentNotDirectory,
ComponentNotFound,
};
const ResolveParent = struct {
parent: *Entity,
basename: []const u8,
};
const ResolveParentError = ParsePathError || ResolvePathError;
pub const DeleteError = ResolveParentError;
pub const OpenError = error {
IsDirectory,
NotDirectory,
} || ParsePathError || ResolvePathError;
pub const ReadDirError = error {
NoMoreItems,
};
pub const CreateError = error {
ExistsAlready,
} || ResolveParentError || Allocator.Error;
root: *Entity,
pub fn init(self: *FileSystem, gpa: Allocator) !void {
self.root = try Entity.initDir(gpa);
self.root.ref();
}
pub fn deinit(self: *FileSystem, gpa: Allocator) void {
self.root.deref(gpa);
}
fn parsePath(path: []const u8, buffer: [][]const u8) ParsePathError![]const []const u8 {
var count: usize = 0;
var cursor: usize = 0;
if (path.len == 0)
return ParsePathError.EmptyPath;
var absolute = false;
if (path[0] == '/') {
absolute = true;
cursor += 1;
}
while (cursor < path.len) {
const start = cursor;
while (cursor < path.len and path[cursor] != '/')
cursor += 1;
const component = path[start..cursor];
if (cursor < path.len)
cursor += 1;
if (std.mem.eql(u8, component, "."))
continue;
if (std.mem.eql(u8, component, "..")) {
if (count == 0)
return ParsePathError.NoRootParent;
count -= 1;
continue;
}
if (count == buffer.len)
return ParsePathError.TooManyComponents;
buffer[count] = component;
count += 1;
}
return buffer[0..count];
}
fn resolvePath(
self : *FileSystem,
components: []const []const u8,
root : ?*Entity,
buffer : []*Entity
) ResolvePathError![]const *Entity {
var count: usize = 1;
buffer[0] = root orelse self.root;
for (components) |component| {
if (count == buffer.len)
return ResolvePathError.ResolutionLimit;
if (!buffer[count-1].is_dir)
return ResolvePathError.ComponentNotDirectory;
const child = buffer[count-1].findChild(component) orelse return ResolvePathError.ComponentNotFound;
buffer[count] = child.addr;
count += 1;
}
return buffer[0..count];
}
fn resolveParent(self: *FileSystem, path: []const u8, root: ?*Entity) !ResolveParent {
var component_buffer: [MAX_PATH_COMPONENTS][]const u8 = undefined;
var resolve_buffer: [MAX_PATH_COMPONENTS]*Entity = undefined;
const components = try parsePath(path, &component_buffer);
if (components.len == 0)
return ResolveParentError.EmptyPath; // TODO: This error may not be right
const path_to_parent = try self.resolvePath(components[0..components.len-1], root, &resolve_buffer);
const parent = path_to_parent[path_to_parent.len-1];
if (!parent.is_dir)
return ResolveParentError.ComponentNotDirectory;
return .{
.parent = parent,
.basename = components[components.len-1],
};
}
fn createAny(self: *FileSystem, path: []const u8, root_dir: ?*OpenDir, is_dir: bool, gpa: Allocator) CreateError!void {
const result = try self.resolveParent(path, if (root_dir) |r| r.entity else null);
const entity = try if (is_dir) Entity.initDir(gpa) else Entity.initFile(gpa);
errdefer gpa.destroy(entity);
try result.parent.addChild(gpa, result.basename, entity);
}
pub fn createFile(self: *FileSystem, path: []const u8, root_dir: ?*OpenDir, gpa: Allocator) CreateError!void {
return self.createAny(path, root_dir, false, gpa);
}
pub fn createDir(self: *FileSystem, path: []const u8, root_dir: ?*OpenDir, gpa: Allocator) CreateError!void {
return self.createAny(path, root_dir, true, gpa);
}
pub fn deleteAny(self: *FileSystem, path: []const u8, root_dir: ?*OpenDir, gpa: Allocator) DeleteError!void {
const result = try self.resolveParent(path, if (root_dir) |r| r.entity else null);
try result.parent.removeChild(gpa, result.basename);
}
fn openAny(
self : *FileSystem,
path : []const u8,
root_dir : ?*OpenDir,
open_dir : *OpenDir,
open_file: *OpenFile
) !bool {
var component_buffer: [MAX_PATH_COMPONENTS][]const u8 = undefined;
var resolve_buffer: [MAX_PATH_COMPONENTS]*Entity = undefined;
const components = try parsePath(path, &component_buffer);
if (components.len == 0)
return OpenError.EmptyPath; // TODO: This error is not right
const resolved = try self.resolvePath(components, if (root_dir) |r| r.entity else null, &resolve_buffer);
if (resolved.len == 0)
return OpenError.EmptyPath; // TODO: This error is not right
const entity = resolved[resolved.len-1];
if (entity.is_dir) {
open_dir.* = .init(entity);
} else {
open_file.* = .init(entity);
}
return entity.is_dir;
}
pub fn openDir(
self : *FileSystem,
path : []const u8,
root_dir: ?*OpenDir,
open_dir: *OpenDir
) OpenError!void {
var dummy: OpenFile = undefined;
const is_dir = try self.openAny(path, root_dir, open_dir, &dummy);
if (!is_dir) {
return OpenError.NotDirectory;
}
open_dir.entity.ref();
}
pub fn openFile(
self : *FileSystem,
path : []const u8,
root_dir : ?*OpenDir,
open_file: *OpenFile
) OpenError!void {
var dummy: OpenDir = undefined;
const is_dir = try self.openAny(path, root_dir, &dummy, open_file);
if (is_dir) {
return OpenError.IsDirectory;
}
open_file.entity.ref();
}
pub fn closeDir(self: *FileSystem, open_dir: *OpenDir, gpa: Allocator) void {
_ = self;
open_dir.entity.deref(gpa);
}
pub fn closeFile(self: *FileSystem, open_file: *OpenFile, gpa: Allocator) void {
_ = self;
open_file.entity.deref(gpa);
}
pub fn readDir(self: *FileSystem, open_dir: *OpenDir) ReadDirError!ReadDir {
_ = self;
if (open_dir.cursor == 0) {
open_dir.cursor += 1;
return .{
.name = ".",
.is_dir = true
};
}
if (open_dir.cursor == 1) {
open_dir.cursor += 1;
return .{
.name = "..",
.is_dir = true
};
}
const index = open_dir.cursor - 2;
if (index == open_dir.entity.children.items.len)
return ReadDirError.NoMoreItems;
const child = open_dir.entity.children.items[index];
const name = child.getName();
@memcpy(open_dir.name[0..name.len], name);
open_dir.cursor += 1;
return .{
.name = open_dir.name[0..name.len],
.is_dir = child.addr.is_dir,
};
}
pub fn readFile(self: *FileSystem, open_file: *OpenFile, offset_maybe: ?usize, target: []u8) usize {
_ = self;
const offset = offset_maybe orelse open_file.cursor;
const source = open_file.entity.bytes.items[offset..];
const num = @min(source.len, target.len);
@memcpy(target[0..num], source[0..num]);
if (offset_maybe == null)
open_file.cursor += num;
return num;
}
pub fn writeFile(
self : *FileSystem,
open_file : *OpenFile,
gpa : Allocator,
offset_maybe: ?usize,
source : []const u8
) Allocator.Error!void {
_ = self;
const offset = offset_maybe orelse open_file.cursor;
const required_capacity = offset + source.len;
const current_capacity = open_file.entity.bytes.items.len;
if (required_capacity > current_capacity)
try open_file.entity.bytes.appendNTimes(gpa, 0, required_capacity - current_capacity);
const target = open_file.entity.bytes.items[offset..required_capacity];
@memcpy(target, source);
if (offset_maybe == null)
open_file.cursor = required_capacity;
}
pub fn fileSize(self: *FileSystem, open_file: *OpenFile) usize {
_ = self;
return open_file.entity.bytes.items.len;
}