Files
Zigmulator/src/node.zig
T

497 lines
14 KiB
Zig

const std = @import("std");
const Allocator = std.mem.Allocator;
const FileSystem = @import("file_system.zig");
const Network = @import("network.zig");
const Scheduler = @import("scheduler.zig");
const ioInterface = @import("io_interface.zig");
const MAX_DESCRIPTORS = 1<<10;
const Node = @This();
const Handle = i32;
pub const TaskID = Scheduler.TaskID;
const NestedEntryPoint = Scheduler.NestedEntryPoint;
const Descriptor = struct {
const Kind = enum {
dir,
file,
listen,
conn,
unused,
};
kind : Kind = .unused,
dir : FileSystem.OpenDir = undefined,
file : FileSystem.OpenFile = undefined,
listen: Network.ListenSocket = undefined,
conn : Network.ConnSocket = undefined,
};
gpa: Allocator,
arena: std.heap.ArenaAllocator,
argv: [][*:0]const u8,
environ_map: std.process.Environ.Map,
scheduler: *Scheduler,
file_system: FileSystem,
network_host: Network.Host,
descriptors: [MAX_DESCRIPTORS]Descriptor,
real_io: std.Io,
stdin_reader: std.Io.File.Reader,
stderr_writer: std.Io.File.Writer,
stdout_writer: std.Io.File.Writer,
stderr_buffer: [1024]u8,
stdout_buffer: [1024]u8,
fn splitCommandArguments(command: []const u8, arena: Allocator) Allocator.Error![][*:0]const u8 {
var cursor: usize = 0;
// Count how many arguments there are
var count: usize = 0;
while (cursor < command.len) {
if (command[cursor] != ' ' and (cursor == 0 or command[cursor-1] == ' '))
count += 1;
cursor += 1;
}
const result = try arena.alloc([*:0]const u8, count);
count = 0;
cursor = 0;
while (true) {
while (cursor < command.len and command[cursor] == ' ')
cursor += 1;
if (cursor == command.len)
break;
const offset = cursor;
while (cursor < command.len and command[cursor] != ' ')
cursor += 1;
const arg = command[offset..cursor];
result[count] = (try arena.dupeZ(u8, arg)).ptr;
count += 1;
}
return result;
}
pub fn init(
self : *Node,
real_io : std.Io,
scheduler: *Scheduler,
network : *Network,
command : []const u8,
addresses: []const u32,
gpa : Allocator
) !void {
self.gpa = gpa;
self.arena = .init(gpa);
self.scheduler = scheduler;
try self.file_system.init(gpa);
self.network_host.init(network, addresses, gpa);
try network.registerHost(&self.network_host);
for (&self.descriptors) |*desc| {
desc.kind = .unused;
}
self.argv = try splitCommandArguments(command, self.arena.allocator());
self.environ_map = try std.process.Environ.createMap(.empty, gpa);
self.real_io = real_io;
self.stdin_reader = std.Io.File.stdin().readerStreaming(real_io, &.{});
self.stdout_writer = std.Io.File.stdout().writerStreaming(real_io, &self.stdout_buffer);
self.stderr_writer = std.Io.File.stderr().writerStreaming(real_io, &self.stderr_buffer);
}
pub fn deinit(self: *Node) void {
self.environ_map.deinit();
self.network_host.deinit();
self.file_system.deinit(self.gpa);
self.arena.deinit();
}
pub fn io(self: *Node) std.Io {
return ioInterface.buildIOInterfaceForNode(self);
}
pub fn processInit(self: *Node) std.process.Init {
return .{
.minimal = .{
.environ = .empty,
.args = .{ .vector = self.argv },
},
.arena = &self.arena,
.gpa = self.gpa, // TODO: Should use a per-task allocator
.io = self.io(),
.environ_map = &self.environ_map,
.preopens = .empty,
};
}
pub fn sleep(self: *Node, delta_us: u64) void {
self.scheduler.sleep(delta_us);
}
pub fn spawn(self: *Node, entry: NestedEntryPoint, context: *const anyopaque) !TaskID {
return self.scheduler.spawnNested(self, entry, context);
}
pub fn despawn(self: *Node, id: TaskID) void {
self.scheduler.despawnNested(id);
}
pub fn cancel(self: *Node, id: TaskID) !void {
try self.scheduler.cancel(id);
}
pub fn wait(self: *Node, ids: []const TaskID) !TaskID {
return self.scheduler.wait(ids);
}
pub fn futexWait(self: *Node, ptr: *const u32, expected: u32) void {
self.scheduler.futexWait(ptr, expected);
}
pub fn futexWake(self: *Node, ptr: *const u32, max_waiters: u32) void {
self.scheduler.futexWake(ptr, max_waiters);
}
fn unusedDesc(self: *Node) ?*Descriptor {
for (&self.descriptors) |*desc| {
if (desc.kind == .unused)
return desc;
}
return null;
}
const HandleError = error {
InvalidHandle,
};
const NUM_SPECIAL_HANDLES = 3;
fn handleToDesc(self: *Node, handle: Handle) HandleError!*Descriptor {
// Any special handle (stdin, stdout, stderr) must be handled
// as special cases before this point.
std.debug.assert(handle >= NUM_SPECIAL_HANDLES);
const index = handle - NUM_SPECIAL_HANDLES;
if (index < 0 or index >= MAX_DESCRIPTORS)
return HandleError.InvalidHandle;
const desc = &self.descriptors[@intCast(index)];
if (desc.kind == .unused)
return HandleError.InvalidHandle;
return desc;
}
fn handleToDescOfType(self: *Node, handle: Handle, kind: Descriptor.Kind) HandleError!*Descriptor {
const desc = try self.handleToDesc(handle);
if (desc.kind != kind)
return HandleError.InvalidHandle;
return desc;
}
fn descToHandle(self: *Node, desc: *Descriptor) Handle {
// TODO: This loop is extremely dumb
for (&self.descriptors, 0..) |*item, i| {
if (item == desc)
return @intCast(i + NUM_SPECIAL_HANDLES);
}
unreachable;
}
pub fn closeDir(self: *Node, handle: Handle) HandleError!void {
self.scheduler.sleep(10);
const desc = try self.handleToDescOfType(handle, .dir);
self.file_system.closeDir(&desc.dir, self.gpa);
desc.kind = .unused;
}
pub const CreateDirError = HandleError || FileSystem.CreateError;
pub fn createDir(self: *Node, parent: ?Handle, path: []const u8) CreateDirError!void {
self.scheduler.sleep(10);
return self.file_system.createDir(
path,
try self.handleToOpenDirOrNULL(parent),
self.gpa
);
}
pub const OpenDirError = error {
DescriptorLimit,
} || HandleError || FileSystem.OpenError;
pub fn openDir(self: *Node, parent: ?Handle, path: []const u8) OpenDirError!Handle {
self.scheduler.sleep(10);
const desc = self.unusedDesc() orelse return OpenDirError.DescriptorLimit;
try self.file_system.openDir(path, try self.handleToOpenDirOrNULL(parent), &desc.dir);
desc.kind = .dir;
return self.descToHandle(desc);
}
pub fn resetDir(self: *Node, handle: Handle) HandleError!void {
self.scheduler.sleep(2);
const desc = try self.handleToDescOfType(handle, .dir);
self.file_system.resetDir(&desc.dir);
}
pub const ReadDirError = HandleError || FileSystem.ReadDirError;
pub fn readDir(self: *Node, handle: Handle) ReadDirError!FileSystem.ReadDir {
self.scheduler.sleep(10);
const desc = try self.handleToDescOfType(handle, .dir);
return self.file_system.readDir(&desc.dir);
}
fn handleToOpenDirOrNULL(self: *Node, handle: ?Handle) HandleError!?*FileSystem.OpenDir {
if (handle) |h| {
const desc = try self.handleToDescOfType(h, .dir);
return &desc.dir;
} else {
return null;
}
}
pub const CreateFileError = HandleError || FileSystem.CreateError;
pub fn createFile(self: *Node, parent: ?Handle, path: []const u8) CreateFileError!void {
self.scheduler.sleep(10);
return self.file_system.createFile(
path,
try self.handleToOpenDirOrNULL(parent),
self.gpa
);
}
pub const DeleteFileError = HandleError || FileSystem.DeleteFileError;
pub fn deleteFile(self: *Node, parent: ?Handle, path: []const u8) DeleteFileError!void {
self.scheduler.sleep(10);
return self.file_system.deleteFile(
path,
try self.handleToOpenDirOrNULL(parent),
self.gpa
);
}
pub const DeleteDirError = HandleError || FileSystem.DeleteDirError;
pub fn deleteDir(self: *Node, parent: ?Handle, path: []const u8) DeleteDirError!void {
self.scheduler.sleep(10);
return self.file_system.deleteDir(
path,
try self.handleToOpenDirOrNULL(parent),
self.gpa
);
}
pub const OpenFileError = error {
DescriptorLimit,
} || HandleError || FileSystem.OpenError;
pub fn openFile(self: *Node, parent: ?Handle, path: []const u8) OpenFileError!Handle {
self.scheduler.sleep(10);
const desc = self.unusedDesc() orelse return OpenFileError.DescriptorLimit;
try self.file_system.openFile(path, try self.handleToOpenDirOrNULL(parent), &desc.file);
desc.kind = .file;
return self.descToHandle(desc);
}
pub fn closeFile(self: *Node, handle: Handle) HandleError!void {
self.scheduler.sleep(10);
const desc = try self.handleToDescOfType(handle, .file);
self.file_system.closeFile(&desc.file, self.gpa);
desc.kind = .unused;
}
pub fn fileSize(self: *Node, handle: Handle) HandleError!u64 {
self.scheduler.sleep(2);
const desc = try self.handleToDescOfType(handle, .file);
return @intCast(self.file_system.fileSize(&desc.file));
}
pub fn readFile(self: *Node, handle: Handle, offset: ?usize, target: []u8) HandleError!usize {
self.scheduler.sleep(100);
if (handle == 0) {
@panic("Not implemented yet"); // TODO: stdin
} else if (handle == 1) {
return HandleError.InvalidHandle;
} else if (handle == 2) {
return HandleError.InvalidHandle;
} else {
const desc = try self.handleToDescOfType(handle, .file);
return self.file_system.readFile(&desc.file, offset, target);
}
}
// It's important this function and the stderr version do not
// return a value back to the simulation or determinism would
// be broken.
fn writeToStdout(self: *Node, source: []const u8) void {
self.stdout_writer.interface.writeAll(source) catch {};
self.stdout_writer.interface.flush() catch {};
}
// See comment on writeToStdout
fn writeToStderr(self: *Node, source: []const u8) void {
self.stderr_writer.interface.writeAll(source) catch {};
self.stderr_writer.interface.flush() catch {};
}
pub const WriteFileError = HandleError || Allocator.Error;
pub fn writeFile(
self : *Node,
handle: Handle,
offset: ?usize,
header: []const u8,
source: []const []const u8
) WriteFileError!usize {
self.scheduler.sleep(100);
if (handle == 0) {
return HandleError.InvalidHandle;
} else if (handle == 1) {
var copied: usize = 0;
self.writeToStdout(header);
copied += header.len;
for (source) |item| {
self.writeToStdout(item);
copied += item.len;
}
return copied;
} else if (handle == 2) {
var copied: usize = 0;
self.writeToStderr(header);
copied += header.len;
for (source) |item| {
self.writeToStderr(item);
copied += item.len;
}
return copied;
} else {
var copied: usize = 0;
const desc = try self.handleToDescOfType(handle, .file);
try self.file_system.writeFile(&desc.file, self.gpa, offset, header);
copied += header.len;
for (source) |item| {
try self.file_system.writeFile(&desc.file, self.gpa, null, item);
copied += item.len;
}
return copied;
}
}
pub const SeekFileError = HandleError || FileSystem.SeekError;
pub fn seekFileTo(self: *Node, handle: Handle, offset: usize) SeekFileError!void {
self.scheduler.sleep(2);
const desc = try self.handleToDescOfType(handle, .file);
self.file_system.seekFileTo(&desc.file, offset);
}
pub fn seekFileBy(self: *Node, handle: Handle, offset: i64) SeekFileError!void {
self.scheduler.sleep(2);
const desc = try self.handleToDescOfType(handle, .file);
try self.file_system.seekFileBy(&desc.file, offset);
}
pub const Address = Network.Address;
pub const ListenError = error {
DescriptorLimit,
} || Network.ListenError;
pub fn listen(self: *Node, address: Address) ListenError!Handle {
self.scheduler.sleep(10);
const desc = self.unusedDesc() orelse return ListenError.DescriptorLimit;
try self.network_host.listen(address, &desc.listen);
desc.kind = .listen;
return self.descToHandle(desc);
}
pub const AcceptError = error {
DescriptorLimit,
} || HandleError || Network.AcceptError;
pub fn accept(self: *Node, handle: Handle) AcceptError!Handle {
self.scheduler.sleep(10);
const old_desc = try self.handleToDescOfType(handle, .listen);
const new_desc = self.unusedDesc() orelse return AcceptError.DescriptorLimit;
try self.network_host.accept(&old_desc.listen, &new_desc.conn);
new_desc.kind = .conn;
return self.descToHandle(new_desc);
}
pub const ConnectError = error {
DescriptorLimit,
} || Network.ConnectError;
pub fn connect(self: *Node, address: Address) ConnectError!Handle {
self.scheduler.sleep(10);
const desc = self.unusedDesc() orelse return ConnectError.DescriptorLimit;
try self.network_host.connect(address, &desc.conn);
desc.kind = .conn;
return self.descToHandle(desc);
}
pub fn readSocket(self: *Node, handle: Handle, target: []u8, block: bool) HandleError!usize {
if (target.len == 0)
return 0;
const desc = try self.handleToDescOfType(handle, .conn);
if (block) {
while (true) {
self.scheduler.sleep(10);
const num = self.network_host.read(&desc.conn, target);
if (num > 0)
return num;
if (num == 0) {
if (!self.network_host.isConnected(&desc.conn))
return 0;
}
}
unreachable;
} else {
return self.network_host.read(&desc.conn, target);
}
}
pub const WriteSocketError = HandleError || Network.SendError;
pub fn writeSocket(self: *Node, handle: Handle, source: []const u8) WriteSocketError!usize {
self.scheduler.sleep(10);
const desc = try self.handleToDescOfType(handle, .conn);
return self.network_host.send(&desc.conn, source);
}
pub fn closeSocket(self: *Node, handle: Handle) HandleError!void {
self.scheduler.sleep(10);
const desc = try self.handleToDesc(handle);
if (desc.kind == .conn) {
self.network_host.closeConnSocket(&desc.conn);
} else if (desc.kind == .listen) {
self.network_host.closeListenSocket(&desc.listen);
} else {
return HandleError.InvalidHandle;
}
desc.kind = .unused;
}
pub fn dumpFiles(self: *Node) void {
self.file_system.dump();
}