// SPDX-FileCopyrightText: 2024 Himbeer // // SPDX-License-Identifier: AGPL-3.0-or-later const std = @import("std"); const Console = @import("Console.zig"); const instructions = @import("instructions.zig"); const mem = @import("mem.zig"); const paging = @import("paging.zig"); const process = @import("process.zig"); const syscall = @import("syscall.zig"); const sysexchange = @import("sysexchange.zig"); const userinit = @import("userinit.zig"); const vfs = @import("vfs.zig"); const Result = sysexchange.Result; const io = std.io; const tar = std.tar; const File = tar.Iterator(io.FixedBufferStream([]const u8).Reader).File; const FileContext = struct { buffer: []const u8, fbs: io.FixedBufferStream([]const u8), }; const iofs = struct { const debug = struct { fn write(ptr: [*]const u8, len: usize) callconv(.C) Result(usize) { // Raise page fault if applicable. if (len > 0) { std.mem.doNotOptimizeAway(ptr[0]); } const procmem: *paging.Table = @ptrFromInt(instructions.satp.read().ppn << 12); const paddr = procmem.translate(@intFromPtr(ptr)).?; const pptr: [*]const u8 = @ptrFromInt(paddr); const bytes = pptr[0..len]; const console = Console.autoChoose() orelse { return Result(usize).fromAnyTypeOrError(Error.NoConsole); }; const w = console.writer(); w.print("{s}", .{bytes}) catch |err| { return Result(usize).fromAnyTypeOrError(err); }; return Result(usize).fromAnyTypeOrError(bytes.len); } }; }; const processfs = struct { const self = struct { fn terminate(pid: u16, thread_id: usize, _: usize) callconv(.C) Result(usize) { const proc = process.findThread(pid, thread_id).?; proc.terminate(); process.schedule() catch |err| { std.debug.panic("Unable to schedule because all processes are terminated: {any}", .{err}); }; } fn id(pid: u16, _: usize, _: usize) callconv(.C) Result(usize) { return Result(usize).fromAnyTypeOrError(pid); } fn threadId(_: u16, thread_id: usize, _: usize) callconv(.C) Result(usize) { return Result(usize).fromAnyTypeOrError(thread_id); } }; const CreationContext = struct { rd: vfs.ResourceDescriptor, buffer: std.ArrayListAligned(u8, paging.page_size), proc: *process.Info, }; fn create(pid: u16, thread_id: usize, data: usize) callconv(.C) Result(usize) { const result = doCreate(pid, thread_id, data); return Result(usize).fromAnyTypeOrError(result); } fn doCreate(pid: u16, thread_id: usize, data: usize) !usize { paging.setUserMemoryAccess(true); defer paging.setUserMemoryAccess(false); const path_c: [*:0]const u8 = @ptrFromInt(data); // fixme: Kernel panic if null pointer const allocator = mem.page_allocator; const rd = try vfs.openNonHookZ(path_c); defer rd.deinit(); if (rd.inode.resource.tag == .dir or rd.inode.resource.tag == .dir_hook) { return vfs.Error.IsAContainer; } const proc = process.findThread(pid, thread_id).?; var buffer = std.ArrayListAligned(u8, paging.page_size).init(allocator); defer buffer.clearAndFree(); try buffer.ensureUnusedCapacity(4096); const ctx = try allocator.create(CreationContext); defer allocator.destroy(ctx); ctx.* = .{ .rd = rd, .buffer = buffer, .proc = proc, }; paging.setUserMemoryAccess(false); defer proc.allowResume(); while (try rd.readHooked(proc, ctx.buffer.items, .{ .hookFn = loadExe, .context = ctx, }) > 0) {} const new_proc = try process.create(allocator, ctx.buffer.items); return new_proc.id; } fn loadExe(context: *anyopaque, driver: *const process.Info) void { const ctx: *CreationContext = @alignCast(@ptrCast(context)); doLoadExe(ctx, driver) catch |err| { sysexchange.frameReturn(usize, &ctx.proc.trap_frame, err); }; } fn doLoadExe(ctx: *CreationContext, driver: *const process.Info) !void { const allocator = mem.page_allocator; defer ctx.proc.allowResume(); const result: Result(usize) = .{ .value = driver.trap_frame.general_purpose_registers[12], .status = @enumFromInt(driver.trap_frame.general_purpose_registers[13]), }; if (result.status != .success) { defer allocator.destroy(ctx); defer ctx.buffer.clearAndFree(); defer ctx.rd.deinit(); sysexchange.frameReturnResult(usize, &ctx.proc.trap_frame, result); return; } const n = result.value; if (n == 0) { defer allocator.destroy(ctx); defer ctx.buffer.clearAndFree(); defer ctx.rd.deinit(); const new_proc = try process.create(allocator, ctx.buffer.items); sysexchange.frameReturn(null, &ctx.proc.trap_frame, new_proc.id); return; } try ctx.buffer.ensureUnusedCapacity(4096); while (try ctx.rd.readHooked(ctx.proc, ctx.buffer.items[ctx.buffer.items.len..], .{ .hookFn = loadExe, .context = ctx, }) > 0) {} const new_proc = try process.create(allocator, ctx.buffer.items); sysexchange.frameReturn(null, &ctx.proc.trap_frame, new_proc.id); } }; pub const Error = error{ NoTarFileInitializer, NoConsole, }; pub fn provideBuiltin(allocator: std.mem.Allocator) !void { try addDir("/io"); try addDir("/userinit"); try addDir("/process"); try addDir("/process/self"); try provideConsole(); try provideUserinit(allocator); try provideProcess(); } fn provideConsole() !void { try vfs.provideResource("/io/debug", .{ .tag = .stream, .data = .{ .stream = .{ .readFn = null, .writeFn = iofs.debug.write, }, }, }, 0, .{ .reclaimable = false, }); } fn provideUserinit(allocator: std.mem.Allocator) !void { var userinit_stream = io.fixedBufferStream(userinit.tarball); const reader = userinit_stream.reader(); var file_name_buffer: [4086]u8 = undefined; var link_name_buffer: [4086]u8 = undefined; var it = tar.iterator(reader, .{ .file_name_buffer = file_name_buffer[0..], .link_name_buffer = link_name_buffer[0..], }); while (try it.next()) |file| { const resolved = try std.fs.path.resolvePosix(allocator, &[_][]const u8{file.name}); const path = try std.fmt.allocPrint(allocator, "/userinit/{s}", .{resolved}); switch (file.kind) { .file => try addFile(path, file), .directory => try addDir(path), .sym_link => {}, } } } fn provideProcess() !void { try vfs.provideResource("/process/create", .{ .tag = .hook, .data = .{ .hook = .{ .callback = processfs.create, }, }, }, 0, .{ .reclaimable = false, }); try provideProcessSelf(); } fn provideProcessSelf() !void { try vfs.provideResource("/process/self/terminate", .{ .tag = .hook, .data = .{ .hook = .{ .callback = processfs.self.terminate, }, }, }, 0, .{ .reclaimable = false, }); try vfs.provideResource("/process/self/id", .{ .tag = .hook, .data = .{ .hook = .{ .callback = processfs.self.id, }, }, }, 0, .{ .reclaimable = false, }); try vfs.provideResource("/process/self/thread_id", .{ .tag = .hook, .data = .{ .hook = .{ .callback = processfs.self.threadId, }, }, }, 0, .{ .reclaimable = false, }); } fn addFile(path: []const u8, file: File) !void { const allocator = vfs.treeRoot().allocator; const buffer = try allocator.alloc(u8, file.size); try file.reader().readNoEof(buffer); const initializer = try allocator.create(FileContext); initializer.* = .{ .buffer = buffer, .fbs = io.fixedBufferStream(@as([]const u8, buffer)), }; try vfs.provideResource(path, .{ .tag = .file, .data = .{ .file = .{ .readFn = read, .writeFn = null, .closeFn = close, .initializer = initializer, }, }, }, 0, .{ .reclaimable = false, }); } fn addDir(path: []const u8) !void { const allocator = vfs.treeRoot().allocator; const tree = try allocator.create(vfs.Tree); tree.* = vfs.Tree.init(allocator); try vfs.provideResource(path, .{ .tag = .dir, .data = .{ .dir = tree }, }, 0, .{ .reclaimable = false, }); } fn open(context: *vfs.FileContext) !void { const allocator = vfs.treeRoot().allocator; const inner = context.inner orelse return Error.NoTarFileInitializer; const old_context: *FileContext = @alignCast(@ptrCast(inner)); const new_context = try allocator.create(FileContext); new_context.* = old_context.*; context.inner = new_context; } fn read(context: *vfs.FileContext, ptr: [*]u8, len: usize) callconv(.C) Result(usize) { if (context.inner == null) { open(context) catch |err| { return Result(usize).fromAnyTypeOrError(err); }; } const inner: *FileContext = @alignCast(@ptrCast(context.inner.?)); const buffer = ptr[0..len]; return Result(usize).fromAnyTypeOrError(inner.fbs.read(buffer)); } fn close(context: *vfs.FileContext) callconv(.C) void { if (context.inner) |inner_opaque| { const allocator = vfs.treeRoot().allocator; const inner: *FileContext = @alignCast(@ptrCast(inner_opaque)); allocator.destroy(inner); } }