// SPDX-FileCopyrightText: 2024 Himbeer // // SPDX-License-Identifier: AGPL-3.0-or-later const std = @import("std"); const paging = @import("paging.zig"); const process = @import("process.zig"); const sysexchange = @import("sysexchange.zig"); const mem = std.mem; var root: Node = .{ .data = .{ .name = "", .resource = .{ .dir = undefined }, .pid = 0 } }; pub const Error = error{ NotFound, RelativePathNotAllowed, NotADirectory, NoAbsoluteContainingDirectory, TooManyReferences, ReadNotSupported, WriteNotSupported, InUse, }; // A stream is a resource that provides a shared data stream with a driver. pub const Stream = struct { readFn: ?ReadFn, writeFn: ?WriteFn, pub const ReadFn = *const fn (ptr: [*]u8, len: usize) callconv(.C) sysexchange.Result(usize); pub const WriteFn = *const fn (ptr: [*]const u8, len: usize) callconv(.C) sysexchange.Result(usize); }; // A file is a resource that creates a unique data stream with a driver. pub const File = struct { openFn: OpenFn, readFn: ?ReadFn, writeFn: ?WriteFn, closeFn: ?CloseFn, pub const OpenFn = *allowzero const fn (pid: u16) callconv(.C) sysexchange.Result(*anyopaque); pub const ReadFn = *const fn (context: *anyopaque, ptr: [*]u8, len: usize) callconv(.C) sysexchange.Result(usize); pub const WriteFn = *const fn (context: *anyopaque, ptr: [*]const u8, len: usize) callconv(.C) sysexchange.Result(usize); pub const CloseFn = *const fn (context: *anyopaque) callconv(.C) void; }; // A hook is a resource that invokes raw driver callbacks when interacted with. pub const Hook = struct { callback: Callback, pub const Callback = *allowzero const fn (pid: u16, data: usize) sysexchange.Result(usize); }; // A directory hook is a resource that provides other resources via driver callbacks. pub const DirHook = struct { provideFn: ProvideFn, findFn: FindFn, removeFn: RemoveFn, pub const ProvideFn = *allowzero const fn (inode: Inode) sysexchange.Result(void); pub const FindFn = *allowzero const fn (name: [*:0]const u8) ?Inode; pub const RemoveFn = *allowzero const fn (name: [*:0]const u8) sysexchange.Result(void); }; pub const Resource = union(enum) { stream: Stream, file: File, hook: Hook, dir: Tree, dir_hook: DirHook, }; pub const Inode = struct { name: []const u8, resource: Resource, refs: usize = 0, pid: u16, }; pub const Node = std.DoublyLinkedList(Inode).Node; pub const Tree = struct { allocator: mem.Allocator, nodes: std.DoublyLinkedList(Inode), pub fn init(allocator: mem.Allocator) Tree { return .{ .allocator = allocator, .nodes = std.DoublyLinkedList(Inode){}, }; } pub fn find(self: *const Tree, name: []const u8) ?*Node { var node = self.nodes.first; while (node) |current_node| { if (current_node.next == current_node) break; if (mem.eql(u8, current_node.data.name, name)) return current_node; node = current_node.next; } return null; } pub fn provideResource(self: *Tree, inode: Inode) !void { var node = try self.allocator.create(Node); node.data = inode; self.nodes.append(node); } pub fn remove(self: *Tree, name: []const u8) !void { if (self.find(name)) |node| { if (node.data.refs > 0) return Error.InUse; self.nodes.remove(node); } else return Error.NotFound; } }; pub const ResourceDescriptor = struct { inode: *Inode, pub fn init(inode: *Inode) !ResourceDescriptor { inode.refs = std.math.add(usize, inode.refs, 1) catch return Error.TooManyReferences; return .{ .inode = inode, }; } pub fn deinit(self: ResourceDescriptor) void { self.inode.refs -|= 1; } pub fn read(self: ResourceDescriptor, proc: *process.Info, buffer: []u8) !noreturn { return switch (self.inode.resource) { .stream => |stream| { const driver = process.latestThread(self.inode.pid).?; const copy = try driver.copyBuffer(buffer); const readFn = stream.readFn orelse return Error.ReadNotSupported; proc.state = .suspended; try call(driver, readFn, .{ copy.ptr, copy.len }, .{ .cleanupFn = moveBack, .buffer = buffer, .copy = copy, }, .{ .hookFn = crossProcessReturn, .context = proc, }); }, .hook => Error.ReadNotSupported, else => Error.ReadNotSupported, }; } pub fn write(self: ResourceDescriptor, proc: *process.Info, bytes: []const u8) !noreturn { return switch (self.inode.resource) { .stream => |stream| { const driver = process.latestThread(self.inode.pid).?; const copy = try driver.copyBytes(bytes); const writeFn = stream.writeFn orelse return Error.WriteNotSupported; proc.state = .suspended; try call(driver, writeFn, .{ copy.ptr, copy.len }, null, .{ .hookFn = crossProcessReturn, .context = proc, }); }, .hook => Error.WriteNotSupported, else => Error.WriteNotSupported, }; } fn moveBack(driver: *const process.Info, buffer: []u8, copy: []const u8) void { paging.setUserMemoryAccess(true); defer paging.setUserMemoryAccess(false); @memcpy(buffer, copy); var addr = @intFromPtr(copy.ptr); const limit = addr + copy.len; while (addr < limit) : (addr += paging.page_size) { driver.page_table.unmapEntry(addr); } } }; pub const UserInfo = union(enum) { rd: ResourceDescriptor, value: sysexchange.Result(usize), }; pub fn init(allocator: mem.Allocator) void { root.data.resource.dir = Tree.init(allocator); } pub fn find(path: []const u8) ?*Node { if (!std.fs.path.isAbsolutePosix(path)) return null; // Error set is empty. var it = std.fs.path.ComponentIterator(.posix, u8).init(path) catch unreachable; var tree = root.data.resource.dir; while (it.next()) |component| { const node = tree.find(component.name) orelse return null; if (mem.eql(u8, component.path, (it.last() orelse return null).path)) { return node; } switch (node.data.resource) { .dir => |dir| tree = dir, else => {}, } } // No components, this is the root directory (/). return &root; } pub fn provideResource(path: []const u8, resource: Resource, pid: u16) !void { if (!std.fs.path.isAbsolutePosix(path)) return Error.RelativePathNotAllowed; const dirname = std.fs.path.dirnamePosix(path) orelse return Error.NoAbsoluteContainingDirectory; if (find(dirname)) |node| { return switch (node.data.resource) { .dir => |*dir| dir.provideResource(.{ .name = std.fs.path.basenamePosix(path), .resource = resource, .pid = pid, }), else => Error.NotADirectory, }; } else return Error.NotFound; } pub fn provideResourceZ(path_c: [*:0]const u8, resource: Resource, pid: u16) !void { return provideResource(mem.sliceTo(path_c, 0), resource, pid); } pub fn open(proc: *process.Info, path: []const u8, pid: u16, data: usize) !ResourceDescriptor { const node = find(path) orelse return Error.NotFound; return switch (node.data.resource) { .hook => |hook| { const driver = process.latestThread(node.data.pid).?; proc.state = .suspended; try call(driver, hook.callback, .{ pid, data }, null, .{ .hookFn = crossProcessReturn, .context = proc, }); }, else => ResourceDescriptor.init(&node.data), }; } pub fn openZ(proc: *process.Info, path_c: [*:0]const u8, pid: u16, data: usize) !ResourceDescriptor { return open(proc, mem.sliceTo(path_c, 0), pid, data); } fn call(proc: *process.Info, function: *const anyopaque, args: anytype, cleanup_hook: ?process.Info.CleanupHook, term_hook: ?process.Info.TermHook) !noreturn { const callback_thread = try proc.createThread(null); callback_thread.call(@intFromPtr(function), args, cleanup_hook, term_hook); } fn crossProcessReturn(context: *anyopaque, driver: *const process.Info) void { const proc: *process.Info = @alignCast(@ptrCast(context)); proc.trap_frame.general_purpose_registers[10] = driver.trap_frame.general_purpose_registers[10]; proc.trap_frame.general_purpose_registers[11] = driver.trap_frame.general_purpose_registers[11]; proc.pc += 4; // Skip ecall instruction proc.state = .waiting; // Scheduler is called by the "terminate" syscall after two layers of returning. }