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;
}
+993
View File
@@ -0,0 +1,993 @@
const std = @import("std");
const Node = @import("node.zig");
const Io = std.Io;
const net = Io.net;
const Dir = Io.Dir;
const File = Io.File;
const Group = Io.Group;
const AnyFuture = Io.AnyFuture;
const ConcurrentError = Io.ConcurrentError;
const CancelProtection = Io.CancelProtection;
const Cancelable = Io.Cancelable;
const Timeout = Io.Timeout;
const Operation = Io.Operation;
const Batch = Io.Batch;
const Terminal = Io.Terminal;
const LockedStderr = Io.LockedStderr;
const Clock = Io.Clock;
const Timestamp = Io.Timestamp;
const Duration = Io.Duration;
const RandomSecureError = Io.RandomSecureError;
const Queue = Io.Queue;
const Allocator = std.mem.Allocator;
pub fn buildIOInterfaceForNode(node: *Node) Io {
return .{
.userdata = node,
.vtable = &.{
.crashHandler = crashHandler,
.async = async,
.concurrent = concurrent,
.await = await,
.cancel = cancel,
.groupAsync = groupAsync,
.groupConcurrent = groupConcurrent,
.groupAwait = groupAwait,
.groupCancel = groupCancel,
.recancel = recancel,
.swapCancelProtection = swapCancelProtection,
.checkCancel = checkCancel,
.futexWait = futexWait,
.futexWaitUncancelable = futexWaitUncancelable,
.futexWake = futexWake,
.operate = operate,
.batchAwaitAsync = batchAwaitAsync,
.batchAwaitConcurrent = batchAwaitConcurrent,
.batchCancel = batchCancel,
.dirCreateDir = dirCreateDir,
.dirCreateDirPath = dirCreateDirPath,
.dirCreateDirPathOpen = dirCreateDirPathOpen,
.dirOpenDir = dirOpenDir,
.dirStat = dirStat,
.dirStatFile = dirStatFile,
.dirAccess = dirAccess,
.dirCreateFile = dirCreateFile,
.dirCreateFileAtomic = dirCreateFileAtomic,
.dirOpenFile = dirOpenFile,
.dirClose = dirClose,
.dirRead = dirRead,
.dirRealPath = dirRealPath,
.dirRealPathFile = dirRealPathFile,
.dirDeleteFile = dirDeleteFile,
.dirDeleteDir = dirDeleteDir,
.dirRename = dirRename,
.dirRenamePreserve = dirRenamePreserve,
.dirSymLink = dirSymLink,
.dirReadLink = dirReadLink,
.dirSetOwner = dirSetOwner,
.dirSetFileOwner = dirSetFileOwner,
.dirSetPermissions = dirSetPermissions,
.dirSetFilePermissions = dirSetFilePermissions,
.dirSetTimestamps = dirSetTimestamps,
.dirHardLink = dirHardLink,
.fileStat = fileStat,
.fileLength = fileLength,
.fileClose = fileClose,
.fileWritePositional = fileWritePositional,
.fileWriteFileStreaming = fileWriteFileStreaming,
.fileWriteFilePositional = fileWriteFilePositional,
.fileReadPositional = fileReadPositional,
.fileSeekBy = fileSeekBy,
.fileSeekTo = fileSeekTo,
.fileSync = fileSync,
.fileIsTty = fileIsTty,
.fileEnableAnsiEscapeCodes = fileEnableAnsiEscapeCodes,
.fileSupportsAnsiEscapeCodes = fileSupportsAnsiEscapeCodes,
.fileSetLength = fileSetLength,
.fileSetOwner = fileSetOwner,
.fileSetPermissions = fileSetPermissions,
.fileSetTimestamps = fileSetTimestamps,
.fileLock = fileLock,
.fileTryLock = fileTryLock,
.fileUnlock = fileUnlock,
.fileDowngradeLock = fileDowngradeLock,
.fileRealPath = fileRealPath,
.fileHardLink = fileHardLink,
.fileMemoryMapCreate = fileMemoryMapCreate,
.fileMemoryMapDestroy = fileMemoryMapDestroy,
.fileMemoryMapSetLength = fileMemoryMapSetLength,
.fileMemoryMapRead = fileMemoryMapRead,
.fileMemoryMapWrite = fileMemoryMapWrite,
.processExecutableOpen = processExecutableOpen,
.processExecutablePath = processExecutablePath,
.lockStderr = lockStderr,
.tryLockStderr = tryLockStderr,
.unlockStderr = unlockStderr,
.processCurrentPath = processCurrentPath,
.processSetCurrentDir = processSetCurrentDir,
.processSetCurrentPath = processSetCurrentPath,
.processReplace = processReplace,
.processReplacePath = processReplacePath,
.processSpawn = processSpawn,
.processSpawnPath = processSpawnPath,
.childWait = childWait,
.childKill = childKill,
.progressParentFile = progressParentFile,
.random = random,
.randomSecure = randomSecure,
.now = now,
.clockResolution = clockResolution,
.sleep = sleep,
.netListenIp = netListenIp,
.netAccept = netAccept,
.netBindIp = netBindIp,
.netConnectIp = netConnectIp,
.netListenUnix = netListenUnix,
.netConnectUnix = netConnectUnix,
.netSocketCreatePair = netSocketCreatePair,
.netSend = netSend,
.netRead = netRead,
.netWrite = netWrite,
.netWriteFile = netWriteFile,
.netClose = netClose,
.netShutdown = netShutdown,
.netInterfaceNameResolve = netInterfaceNameResolve,
.netInterfaceName = netInterfaceName,
.netLookup = netLookup,
},
};
}
fn crashHandler(userdata: ?*anyopaque) void {
_ = userdata;
@panic("Not implemented yet");
}
fn async(
userdata: ?*anyopaque,
result: []u8,
result_alignment: std.mem.Alignment,
context: []const u8,
context_alignment: std.mem.Alignment,
start: *const fn (context: *const anyopaque, result: *anyopaque) void,
) ?*AnyFuture {
_ = userdata;
_ = result;
_ = result_alignment;
_ = context;
_ = context_alignment;
_ = start;
@panic("Not implemented yet");
}
fn concurrent(
userdata: ?*anyopaque,
result_len: usize,
result_alignment: std.mem.Alignment,
context: []const u8,
context_alignment: std.mem.Alignment,
start: *const fn (context: *const anyopaque, result: *anyopaque) void,
) ConcurrentError!*AnyFuture {
_ = userdata;
_ = result_len;
_ = result_alignment;
_ = context;
_ = context_alignment;
_ = start;
@panic("Not implemented yet");
}
fn await(
userdata: ?*anyopaque,
future: *AnyFuture,
result: []u8,
result_alignment: std.mem.Alignment,
) void {
_ = userdata;
_ = future;
_ = result;
_ = result_alignment;
@panic("Not implemented yet");
}
fn cancel(
userdata: ?*anyopaque,
future: *AnyFuture,
result: []u8,
result_alignment: std.mem.Alignment,
) void {
_ = userdata;
_ = future;
_ = result;
_ = result_alignment;
@panic("Not implemented yet");
}
fn groupAsync(
userdata: ?*anyopaque,
type_erased: *Group,
context: []const u8,
context_alignment: std.mem.Alignment,
start: *const fn (context: *const anyopaque) void,
) void {
_ = userdata;
_ = type_erased;
_ = context;
_ = context_alignment;
_ = start;
@panic("Not implemented yet");
}
fn groupConcurrent(
userdata: ?*anyopaque,
type_erased: *Group,
context: []const u8,
context_alignment: std.mem.Alignment,
start: *const fn (context: *const anyopaque) void,
) ConcurrentError!void {
_ = userdata;
_ = type_erased;
_ = context;
_ = context_alignment;
_ = start;
@panic("Not implemented yet");
}
fn groupAwait(userdata: ?*anyopaque, type_erased: *Group, initial_token: *anyopaque) Cancelable!void {
_ = userdata;
_ = type_erased;
_ = initial_token;
@panic("Not implemented yet");
}
fn groupCancel(userdata: ?*anyopaque, type_erased: *Group, initial_token: *anyopaque) void {
_ = userdata;
_ = type_erased;
_ = initial_token;
@panic("Not implemented yet");
}
fn recancel(userdata: ?*anyopaque) void {
_ = userdata;
@panic("Not implemented yet");
}
fn swapCancelProtection(userdata: ?*anyopaque, new: CancelProtection) CancelProtection {
_ = userdata;
_ = new;
@panic("Not implemented yet");
}
fn checkCancel(userdata: ?*anyopaque) Cancelable!void {
_ = userdata;
@panic("Not implemented yet");
}
fn futexWait(userdata: ?*anyopaque, ptr: *const u32, expected: u32, timeout: Timeout) Cancelable!void {
_ = userdata;
_ = ptr;
_ = expected;
_ = timeout;
@panic("Not implemented yet");
}
fn futexWaitUncancelable(userdata: ?*anyopaque, ptr: *const u32, expected: u32) void {
_ = userdata;
_ = ptr;
_ = expected;
@panic("Not implemented yet");
}
fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void {
_ = userdata;
_ = ptr;
_ = max_waiters;
@panic("Not implemented yet");
}
fn operate(userdata: ?*anyopaque, operation: Operation) Cancelable!Operation.Result {
const node: *Node = @ptrCast(@alignCast(userdata.?));
return switch (operation) {
.file_write_streaming => |op| .{
.file_write_streaming = fileWriteStreaming(node, op),
},
else => @panic("Not implemented yet"),
};
}
fn fileWriteStreaming(node: *Node, op: Operation.FileWriteStreaming) Operation.FileWriteStreaming.Result {
var copied: usize = 0;
copied += writeFile(node, op.file, null, op.header, op.data[0 .. op.data.len - 1]) catch |err| return err;
const pattern = op.data[op.data.len - 1];
for (0..op.splat) |_| {
copied += writeFile(node, op.file, null, &.{}, &.{pattern}) catch |err| return err;
}
return copied;
}
fn writeFile(node: *Node, file: File, offset: ?usize, header: []const u8, data: []const []const u8) Operation.FileWriteStreaming.Error!usize {
return node.writeFile(file.handle, offset, header, data) catch |err| switch (err) {
error.InvalidHandle => error.NotOpenForWriting,
error.OutOfMemory => error.SystemResources,
};
}
fn batchAwaitAsync(userdata: ?*anyopaque, batch: *Batch) Cancelable!void {
_ = userdata;
_ = batch;
@panic("Not implemented yet");
}
fn batchAwaitConcurrent(userdata: ?*anyopaque, batch: *Batch, timeout: Timeout) Batch.AwaitConcurrentError!void {
_ = userdata;
_ = batch;
_ = timeout;
@panic("Not implemented yet");
}
fn batchCancel(userdata: ?*anyopaque, batch: *Batch) void {
_ = userdata;
_ = batch;
@panic("Not implemented yet");
}
fn dirCreateDir(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void {
_ = userdata;
_ = dir;
_ = sub_path;
_ = permissions;
@panic("Not implemented yet");
}
fn dirCreateDirPath(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirPathError!Dir.CreatePathStatus {
_ = userdata;
_ = dir;
_ = sub_path;
_ = permissions;
@panic("Not implemented yet");
}
fn dirCreateDirPathOpen(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions, options: Dir.OpenOptions) Dir.CreateDirPathOpenError!Dir {
_ = userdata;
_ = dir;
_ = sub_path;
_ = permissions;
_ = options;
@panic("Not implemented yet");
}
fn dirOpenDir(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, options: Dir.OpenOptions) Dir.OpenError!Dir {
_ = userdata;
_ = dir;
_ = sub_path;
_ = options;
@panic("Not implemented yet");
}
fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat {
_ = userdata;
_ = dir;
@panic("Not implemented yet");
}
fn dirStatFile(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, options: Dir.StatFileOptions) Dir.StatFileError!File.Stat {
_ = userdata;
_ = dir;
_ = sub_path;
_ = options;
@panic("Not implemented yet");
}
fn dirAccess(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, options: Dir.AccessOptions) Dir.AccessError!void {
_ = userdata;
_ = dir;
_ = sub_path;
_ = options;
@panic("Not implemented yet");
}
fn dirCreateFile(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, flags: Dir.CreateFileOptions) File.OpenError!File {
_ = userdata;
_ = dir;
_ = sub_path;
_ = flags;
@panic("Not implemented yet");
}
fn dirCreateFileAtomic(userdata: ?*anyopaque, dir: Dir, dest_path: []const u8, options: Dir.CreateFileAtomicOptions) Dir.CreateFileAtomicError!File.Atomic {
_ = userdata;
_ = dir;
_ = dest_path;
_ = options;
@panic("Not implemented yet");
}
fn dirOpenFile(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, flags: Dir.OpenFileOptions) File.OpenError!File {
_ = userdata;
_ = dir;
_ = sub_path;
_ = flags;
@panic("Not implemented yet");
}
fn dirClose(userdata: ?*anyopaque, dirs: []const Dir) void {
_ = userdata;
_ = dirs;
@panic("Not implemented yet");
}
fn dirRead(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
_ = userdata;
_ = dr;
_ = buffer;
@panic("Not implemented yet");
}
fn dirRealPath(userdata: ?*anyopaque, dir: Dir, out_buffer: []u8) Dir.RealPathError!usize {
_ = userdata;
_ = dir;
_ = out_buffer;
@panic("Not implemented yet");
}
fn dirRealPathFile(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_buffer: []u8) Dir.RealPathFileError!usize {
_ = userdata;
_ = dir;
_ = sub_path;
_ = out_buffer;
@panic("Not implemented yet");
}
fn dirDeleteFile(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteFileError!void {
_ = userdata;
_ = dir;
_ = sub_path;
@panic("Not implemented yet");
}
fn dirDeleteDir(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteDirError!void {
_ = userdata;
_ = dir;
_ = sub_path;
@panic("Not implemented yet");
}
fn dirRename(userdata: ?*anyopaque, old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) Dir.RenameError!void {
_ = userdata;
_ = old_dir;
_ = old_sub_path;
_ = new_dir;
_ = new_sub_path;
@panic("Not implemented yet");
}
fn dirRenamePreserve(userdata: ?*anyopaque, old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) Dir.RenamePreserveError!void {
_ = userdata;
_ = old_dir;
_ = old_sub_path;
_ = new_dir;
_ = new_sub_path;
@panic("Not implemented yet");
}
fn dirSymLink(userdata: ?*anyopaque, dir: Dir, target_path: []const u8, sym_link_path: []const u8, flags: Dir.SymLinkFlags) Dir.SymLinkError!void {
_ = userdata;
_ = dir;
_ = target_path;
_ = sym_link_path;
_ = flags;
@panic("Not implemented yet");
}
fn dirReadLink(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize {
_ = userdata;
_ = dir;
_ = sub_path;
_ = buffer;
@panic("Not implemented yet");
}
fn dirSetOwner(userdata: ?*anyopaque, dir: Dir, owner: ?File.Uid, group: ?File.Gid) Dir.SetOwnerError!void {
_ = userdata;
_ = dir;
_ = owner;
_ = group;
@panic("Not implemented yet");
}
fn dirSetFileOwner(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, owner: ?File.Uid, group: ?File.Gid, options: Dir.SetFileOwnerOptions) Dir.SetFileOwnerError!void {
_ = userdata;
_ = dir;
_ = sub_path;
_ = owner;
_ = group;
_ = options;
@panic("Not implemented yet");
}
fn dirSetPermissions(userdata: ?*anyopaque, dir: Dir, permissions: Dir.Permissions) Dir.SetPermissionsError!void {
_ = userdata;
_ = dir;
_ = permissions;
@panic("Not implemented yet");
}
fn dirSetFilePermissions(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: File.Permissions, options: Dir.SetFilePermissionsOptions) Dir.SetFilePermissionsError!void {
_ = userdata;
_ = dir;
_ = sub_path;
_ = permissions;
_ = options;
@panic("Not implemented yet");
}
fn dirSetTimestamps(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, options: Dir.SetTimestampsOptions) Dir.SetTimestampsError!void {
_ = userdata;
_ = dir;
_ = sub_path;
_ = options;
@panic("Not implemented yet");
}
fn dirHardLink(userdata: ?*anyopaque, old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8, options: Dir.HardLinkOptions) Dir.HardLinkError!void {
_ = userdata;
_ = old_dir;
_ = old_sub_path;
_ = new_dir;
_ = new_sub_path;
_ = options;
@panic("Not implemented yet");
}
fn fileStat(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
_ = userdata;
_ = file;
@panic("Not implemented yet");
}
fn fileLength(userdata: ?*anyopaque, file: File) File.LengthError!u64 {
_ = userdata;
_ = file;
@panic("Not implemented yet");
}
fn fileClose(userdata: ?*anyopaque, files: []const File) void {
_ = userdata;
_ = files;
@panic("Not implemented yet");
}
fn fileWritePositional(
userdata: ?*anyopaque,
file : File,
header : []const u8,
data : []const []const u8,
splat : usize,
offset : u64
) File.WritePositionalError!usize {
_ = userdata;
_ = file;
_ = header;
_ = data;
_ = splat;
_ = offset;
@panic("Not implemented yet");
}
fn fileWriteFileStreaming(userdata: ?*anyopaque, file: File, header: []const u8, file_reader: *Io.File.Reader, limit: Io.Limit) File.Writer.WriteFileError!usize {
_ = userdata;
_ = file;
_ = header;
_ = file_reader;
_ = limit;
@panic("Not implemented yet");
}
fn fileWriteFilePositional(userdata: ?*anyopaque, file: File, header: []const u8, file_reader: *Io.File.Reader, limit: Io.Limit, offset: u64) File.WriteFilePositionalError!usize {
_ = userdata;
_ = file;
_ = header;
_ = file_reader;
_ = limit;
_ = offset;
@panic("Not implemented yet");
}
fn fileReadPositional(userdata: ?*anyopaque, file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize {
_ = userdata;
_ = file;
_ = data;
_ = offset;
@panic("Not implemented yet");
}
fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!void {
_ = userdata;
_ = file;
_ = offset;
@panic("Not implemented yet");
}
fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!void {
_ = userdata;
_ = file;
_ = offset;
@panic("Not implemented yet");
}
fn fileSync(userdata: ?*anyopaque, file: File) File.SyncError!void {
_ = userdata;
_ = file;
@panic("Not implemented yet");
}
fn fileIsTty(userdata: ?*anyopaque, file: File) Cancelable!bool {
_ = userdata;
_ = file;
@panic("Not implemented yet");
}
fn fileEnableAnsiEscapeCodes(userdata: ?*anyopaque, file: File) File.EnableAnsiEscapeCodesError!void {
_ = userdata;
_ = file;
@panic("Not implemented yet");
}
fn fileSupportsAnsiEscapeCodes(userdata: ?*anyopaque, file: File) Cancelable!bool {
_ = userdata;
_ = file;
@panic("Not implemented yet");
}
fn fileSetLength(userdata: ?*anyopaque, file: File, length: u64) File.SetLengthError!void {
_ = userdata;
_ = file;
_ = length;
@panic("Not implemented yet");
}
fn fileSetOwner(userdata: ?*anyopaque, file: File, owner: ?File.Uid, group: ?File.Gid) File.SetOwnerError!void {
_ = userdata;
_ = file;
_ = owner;
_ = group;
@panic("Not implemented yet");
}
fn fileSetPermissions(userdata: ?*anyopaque, file: File, permissions: File.Permissions) File.SetPermissionsError!void {
_ = userdata;
_ = file;
_ = permissions;
@panic("Not implemented yet");
}
fn fileSetTimestamps(userdata: ?*anyopaque, file: File, options: File.SetTimestampsOptions) File.SetTimestampsError!void {
_ = userdata;
_ = file;
_ = options;
@panic("Not implemented yet");
}
fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!void {
_ = userdata;
_ = file;
_ = lock;
@panic("Not implemented yet");
}
fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!bool {
_ = userdata;
_ = file;
_ = lock;
@panic("Not implemented yet");
}
fn fileUnlock(userdata: ?*anyopaque, file: File) void {
_ = userdata;
_ = file;
@panic("Not implemented yet");
}
fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError!void {
_ = userdata;
_ = file;
@panic("Not implemented yet");
}
fn fileRealPath(userdata: ?*anyopaque, file: File, out_buffer: []u8) File.RealPathError!usize {
_ = userdata;
_ = file;
_ = out_buffer;
@panic("Not implemented yet");
}
fn fileHardLink(userdata: ?*anyopaque, file: File, new_dir: Dir, new_sub_path: []const u8, options: File.HardLinkOptions) File.HardLinkError!void {
_ = userdata;
_ = file;
_ = new_dir;
_ = new_sub_path;
_ = options;
@panic("Not implemented yet");
}
fn fileMemoryMapCreate(userdata: ?*anyopaque, file: File, options: File.MemoryMap.CreateOptions) File.MemoryMap.CreateError!File.MemoryMap {
_ = userdata;
_ = file;
_ = options;
@panic("Not implemented yet");
}
fn fileMemoryMapDestroy(userdata: ?*anyopaque, mm: *File.MemoryMap) void {
_ = userdata;
_ = mm;
@panic("Not implemented yet");
}
fn fileMemoryMapSetLength(userdata: ?*anyopaque, mm: *File.MemoryMap, new_len: usize) File.MemoryMap.SetLengthError!void {
_ = userdata;
_ = mm;
_ = new_len;
@panic("Not implemented yet");
}
fn fileMemoryMapRead(userdata: ?*anyopaque, mm: *File.MemoryMap) File.ReadPositionalError!void {
_ = userdata;
_ = mm;
@panic("Not implemented yet");
}
fn fileMemoryMapWrite(userdata: ?*anyopaque, mm: *File.MemoryMap) File.WritePositionalError!void {
_ = userdata;
_ = mm;
@panic("Not implemented yet");
}
fn processExecutableOpen(userdata: ?*anyopaque, flags: Dir.OpenFileOptions) std.process.OpenExecutableError!File {
_ = userdata;
_ = flags;
@panic("Not implemented yet");
}
fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.ExecutablePathError!usize {
_ = userdata;
_ = out_buffer;
@panic("Not implemented yet");
}
fn lockStderr(userdata: ?*anyopaque, terminal_mode: ?Terminal.Mode) Cancelable!LockedStderr {
_ = userdata;
_ = terminal_mode;
@panic("Not implemented yet");
}
fn tryLockStderr(userdata: ?*anyopaque, terminal_mode: ?Terminal.Mode) Cancelable!?LockedStderr {
_ = userdata;
_ = terminal_mode;
@panic("Not implemented yet");
}
fn unlockStderr(userdata: ?*anyopaque) void {
_ = userdata;
@panic("Not implemented yet");
}
fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) std.process.CurrentPathError!usize {
_ = userdata;
_ = buffer;
@panic("Not implemented yet");
}
fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) std.process.SetCurrentDirError!void {
_ = userdata;
_ = dir;
@panic("Not implemented yet");
}
fn processSetCurrentPath(userdata: ?*anyopaque, dir_path: []const u8) std.process.SetCurrentPathError!void {
_ = userdata;
_ = dir_path;
@panic("Not implemented yet");
}
fn processReplace(userdata: ?*anyopaque, options: std.process.ReplaceOptions) std.process.ReplaceError {
_ = userdata;
_ = options;
@panic("Not implemented yet");
}
fn processReplacePath(userdata: ?*anyopaque, dir: Dir, options: std.process.ReplaceOptions) std.process.ReplaceError {
_ = userdata;
_ = dir;
_ = options;
@panic("Not implemented yet");
}
fn processSpawn(userdata: ?*anyopaque, options: std.process.SpawnOptions) std.process.SpawnError!std.process.Child {
_ = userdata;
_ = options;
@panic("Not implemented yet");
}
fn processSpawnPath(userdata: ?*anyopaque, dir: Dir, options: std.process.SpawnOptions) std.process.SpawnError!std.process.Child {
_ = userdata;
_ = dir;
_ = options;
@panic("Not implemented yet");
}
fn childWait(userdata: ?*anyopaque, child: *std.process.Child) std.process.Child.WaitError!std.process.Child.Term {
_ = userdata;
_ = child;
@panic("Not implemented yet");
}
fn childKill(userdata: ?*anyopaque, child: *std.process.Child) void {
_ = userdata;
_ = child;
@panic("Not implemented yet");
}
fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {
_ = userdata;
@panic("Not implemented yet");
}
fn now(userdata: ?*anyopaque, clock: Clock) Timestamp {
_ = userdata;
_ = clock;
@panic("Not implemented yet");
}
fn clockResolution(userdata: ?*anyopaque, clock: Clock) Clock.ResolutionError!Duration {
_ = userdata;
_ = clock;
@panic("Not implemented yet");
}
fn sleep(userdata: ?*anyopaque, timeout: Timeout) Cancelable!void {
_ = userdata;
_ = timeout;
@panic("Not implemented yet");
}
fn random(userdata: ?*anyopaque, buffer: []u8) void {
_ = userdata;
_ = buffer;
@panic("Not implemented yet");
}
fn randomSecure(userdata: ?*anyopaque, buffer: []u8) RandomSecureError!void {
_ = userdata;
_ = buffer;
@panic("Not implemented yet");
}
fn netListenIp(userdata: ?*anyopaque, address: *const net.IpAddress, options: net.IpAddress.ListenOptions) net.IpAddress.ListenError!net.Socket {
_ = userdata;
_ = address;
_ = options;
@panic("Not implemented yet");
}
fn netAccept(userdata: ?*anyopaque, listen_handle: net.Socket.Handle, options: net.Server.AcceptOptions) net.Server.AcceptError!net.Socket {
_ = userdata;
_ = listen_handle;
_ = options;
@panic("Not implemented yet");
}
fn netBindIp(userdata: ?*anyopaque, address: *const net.IpAddress, options: net.IpAddress.BindOptions) net.IpAddress.BindError!net.Socket {
_ = userdata;
_ = address;
_ = options;
@panic("Not implemented yet");
}
fn netConnectIp(userdata: ?*anyopaque, address: *const net.IpAddress, options: net.IpAddress.ConnectOptions) net.IpAddress.ConnectError!net.Socket {
_ = userdata;
_ = address;
_ = options;
@panic("Not implemented yet");
}
fn netListenUnix(userdata: ?*anyopaque, address: *const net.UnixAddress, options: net.UnixAddress.ListenOptions) net.UnixAddress.ListenError!net.Socket.Handle {
_ = userdata;
_ = address;
_ = options;
@panic("Not implemented yet");
}
fn netConnectUnix(userdata: ?*anyopaque, address: *const net.UnixAddress) net.UnixAddress.ConnectError!net.Socket.Handle {
_ = userdata;
_ = address;
@panic("Not implemented yet");
}
fn netSocketCreatePair(userdata: ?*anyopaque, options: net.Socket.CreatePairOptions) net.Socket.CreatePairError![2]net.Socket {
_ = userdata;
_ = options;
@panic("Not implemented yet");
}
fn netSend(userdata: ?*anyopaque, handle: net.Socket.Handle, messages: []net.OutgoingMessage, flags: net.SendFlags) struct { ?net.Socket.SendError, usize } {
_ = userdata;
_ = handle;
_ = messages;
_ = flags;
@panic("Not implemented yet");
}
/// Returns 0 on end of stream.
fn netRead(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize {
_ = userdata;
_ = fd;
_ = data;
@panic("Not implemented yet");
}
fn netWrite(userdata: ?*anyopaque, handle: net.Socket.Handle, header: []const u8, data: []const []const u8, splat: usize) net.Stream.Writer.Error!usize {
_ = userdata;
_ = handle;
_ = header;
_ = data;
_ = splat;
@panic("Not implemented yet");
}
fn netWriteFile(userdata: ?*anyopaque, socket_handle: net.Socket.Handle, header: []const u8, file_reader: *Io.File.Reader, limit: Io.Limit) net.Stream.Writer.WriteFileError!usize {
_ = userdata;
_ = socket_handle;
_ = header;
_ = file_reader;
_ = limit;
@panic("Not implemented yet");
}
fn netClose(userdata: ?*anyopaque, handles: []const net.Socket.Handle) void {
_ = userdata;
_ = handles;
@panic("Not implemented yet");
}
fn netShutdown(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.ShutdownHow) net.ShutdownError!void {
_ = userdata;
_ = handle;
_ = how;
@panic("Not implemented yet");
}
fn netInterfaceNameResolve(userdata: ?*anyopaque, name: *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface {
_ = userdata;
_ = name;
@panic("Not implemented yet");
}
fn netInterfaceName(userdata: ?*anyopaque, interface: net.Interface) net.Interface.NameError!net.Interface.Name {
_ = userdata;
_ = interface;
@panic("Not implemented yet");
}
fn netLookup(userdata: ?*anyopaque, host_name: net.HostName, resolved: *Queue(net.HostName.LookupResult), options: net.HostName.LookupOptions) net.HostName.LookupError!void {
_ = userdata;
_ = host_name;
_ = resolved;
_ = options;
@panic("Not implemented yet");
}
+53
View File
@@ -0,0 +1,53 @@
const std = @import("std");
const Io = std.Io;
const Simulator = @import("simulator.zig");
fn programA(init: std.process.Init) anyerror!void {
var stdout = Io.File.stdout().writerStreaming(init.io, &.{});
for (0..3) |_| {
try stdout.interface.print("Hello from program A!\n", .{});
try stdout.interface.flush();
}
}
fn programB(init: std.process.Init) anyerror!void {
var stdout = Io.File.stdout().writerStreaming(init.io, &.{});
for (0..3) |_| {
try stdout.interface.print("Hello from program B!\n", .{});
try stdout.interface.flush();
}
}
fn programC(init: std.process.Init) anyerror!void {
var stdout = Io.File.stdout().writerStreaming(init.io, &.{});
for (0..3) |_| {
try stdout.interface.print("Hello from program C!\n", .{});
try stdout.interface.flush();
}
}
pub fn main(init: std.process.Init) !void {
var sim: Simulator = undefined;
sim.init(std.heap.page_allocator, init.io);
defer sim.deinit();
// Associate executable names to zig entry functions
try sim.addExecutable("program_a", programA);
try sim.addExecutable("program_b", programB);
try sim.addExecutable("program_c", programC);
// Now run commands in the form:
// program_name arg1 arg2 arg3 ...
// where program_name is one of the registered functions.
try sim.spawn("program_a", .{});
try sim.spawn("program_b", .{});
try sim.spawn("program_c", .{});
// Advance the cluster's state by advancing the program's
// states one by one. Exits when all programs have returned
// or failed.
while (sim.scheduleOne()) {}
std.debug.print("Simulation ended\n", .{});
}
+247
View File
@@ -0,0 +1,247 @@
const std = @import("std");
const Network = @This();
const Allocator = std.mem.Allocator;
pub const Address = struct {
ipv4: u32,
port: u16,
pub fn eql(self: Address, other: Address) bool {
return self.ipv4 == other.ipv4
and self.port == other.port;
}
};
pub const ListenError = error {
AddressNotAvailable,
AddressAlreadyUsed,
} || Allocator.Error;
pub const AcceptError = error {
AcceptQueueEmpty,
} || Allocator.Error;
pub const ConnectError = error {
UnavailableHost,
PeerNotListeningOnAddress,
} || Allocator.Error;
pub const SendError = Allocator.Error;
pub const ReadError = error {};
pub const ListenSocket = struct {
next: ?*ListenSocket,
address: Address,
accept_queue: std.ArrayList(*ConnSocket),
};
pub const ConnSocket = struct {
next: ?*ConnSocket,
peer_listen: ?*ListenSocket,
peer_conn : ?*ConnSocket,
input_buffer: std.ArrayList(u8),
output_buffer: std.ArrayList(u8),
};
pub const Host = struct {
gpa: Allocator,
// Parent network system
network: *Network,
listen_list: ?*ListenSocket,
conn_list: ?*ConnSocket,
available_addresses_ipv4: []const u32,
pub fn init(self: *Host, network: *Network, addresses: []const u32, gpa: Allocator) void {
self.gpa = gpa;
self.network = network;
self.listen_list = null;
self.conn_list = null;
self.available_addresses_ipv4 = addresses;
}
pub fn deinit(self: *Host) void {
_ = self;
}
fn linkConnSocket(self: *Host, socket: *ConnSocket) void {
socket.next = self.conn_list;
self.conn_list = socket;
}
fn unlinkConnSocket(self: *Host, socket: *ConnSocket) void {
var cursor = self.conn_list.*; // Note that the list can't be empty or we wouldn't be unlinking
if (cursor == socket) {
self.conn_list = null;
} else {
while (cursor.next != socket)
cursor = cursor.next.*;
}
}
fn addressCurrentlyUsed(self: *Host, address: Address) bool {
var socket = self.listen_list;
while (socket) |s| {
if (s.address.eql(address))
return true;
socket = s.next;
}
return false;
}
pub fn isAddressAvailable(self: *Host, ipv4: u32) bool {
for (self.available_addresses_ipv4) |item| {
if (ipv4 == item)
return true;
}
return false;
}
pub fn listen(self: *Host, address: Address, socket: *ListenSocket) ListenError!void {
if (!self.isAddressAvailable(address.ipv4))
return ListenError.AddressNotAvailable;
if (self.addressCurrentlyUsed(address))
return ListenError.AddressAlreadyUsed;
socket.address = address;
socket.accept_queue = .empty;
// Add listen socket to the list
socket.next = self.listen_list;
self.listen_list = socket;
}
pub fn accept(self: *Host, socket: *ListenSocket, new_socket: *ConnSocket) AcceptError!void {
// Pop a connectioon from the listener's accept queue
if (socket.accept_queue.items.len == 0)
return AcceptError.AcceptQueueEmpty;
const peer_socket = socket.accept_queue.orderedRemove(0);
// Add newly created socket to the connection socket list
self.linkConnSocket(new_socket);
errdefer self.unlinkConnSocket(new_socket);
// Link the bound sockets and remove the reference to the listener
new_socket.peer_conn = peer_socket;
peer_socket.peer_conn = new_socket;
peer_socket.peer_listen = null;
// Initialize other fields
new_socket.peer_listen = null;
new_socket.input_buffer = .empty;
new_socket.output_buffer = .empty;
}
fn findListenSocket(self: *Host, address: Address) ?*ListenSocket {
var socket = self.listen_list;
while (socket) |s| {
if (s.address.eql(address))
return s;
socket = s.next;
}
return null;
}
pub fn connect(self: *Host, address: Address, new_socket: *ConnSocket) ConnectError!void {
const host = self.network.findHostByIPv4(address.ipv4) orelse return ConnectError.UnavailableHost;
const listen_socket = host.findListenSocket(address) orelse return ConnectError.PeerNotListeningOnAddress;
// Add newly created socket to the connection socket list
self.linkConnSocket(new_socket);
errdefer self.unlinkConnSocket(new_socket);
new_socket.peer_listen = listen_socket;
new_socket.peer_conn = null;
new_socket.input_buffer = .empty;
new_socket.output_buffer = .empty;
// Add socket to the peer's accept queue
try listen_socket.accept_queue.append(self.gpa, new_socket);
}
pub fn closeConnSocket(self: *Host, socket: *ConnSocket) void {
if (socket.peer_listen) |peer| {
for (peer.accept_queue.items, 0..) |item, i| {
if (item == socket) {
_ = peer.accept_queue.orderedRemove(i);
break;
}
}
}
if (socket.peer_conn) |peer| {
peer.peer_conn = null;
}
self.unlinkConnSocket(socket);
socket.input_buffer.deinit(self.gpa);
socket.output_buffer.deinit(self.gpa);
}
pub fn closeListenSocket(self: *Host, socket: *ListenSocket) void {
for (socket.accept_queue.items) |peer| {
peer.peer_listen = null;
}
self.unlinkListenSocket(socket);
socket.accept_queue.deinit(self.gpa);
}
pub fn send(self: *Host, socket: *ConnSocket, source: []const u8) usize {
var n: usize = 0;
for (source) |c| {
socket.output_buffer.append(self.gpa, c) catch break;
n += 1;
}
return n;
}
pub fn read(_: *Host, socket: *ConnSocket, target: []u8) usize {
const num = @min(target.len, socket.input_buffer.items.len);
@memcpy(target[0..num], socket.input_buffer.items[0..num]);
for (0..num) |_| {
socket.input_buffer.orderedRemove(0);
}
return num;
}
};
gpa: Allocator,
hosts: std.ArrayList(*Host),
pub fn init(self: *Network, gpa: Allocator) void {
self.gpa = gpa;
self.hosts = .empty;
}
pub fn deinit(self: *Network) void {
self.hosts.deinit(self.gpa);
}
pub fn registerHost(self: *Network, host: *Host) Allocator.Error!void {
try self.hosts.append(self.gpa, host);
host.network = self;
}
pub fn findHostByIPv4(self: *Network, ipv4: u32) ?*Host {
for (self.hosts.items) |host| {
if (host.isAddressAvailable(ipv4))
return host;
}
return null;
}
+383
View File
@@ -0,0 +1,383 @@
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;
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,
};
}
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 fn readDir(self: *Node, handle: Handle) HandleError!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.DeleteError;
pub fn deleteFile(self: *Node, parent: ?Handle, path: []const u8) DeleteFileError!void {
self.scheduler.sleep(10);
return self.file_system.deleteAny(
path,
try self.handleToOpenDirOrNULL(parent),
self.gpa
);
}
pub const OpenFileError = error {
DescriptorLimit,
} || FileSystem.OpenError;
pub fn openFile(self: *Node, parent: ?Handle, path: []const u8) !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 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) HandleError!usize {
self.scheduler.sleep(10);
const desc = try self.handleToDescOfType(handle, .conn);
return self.network_host.read(&desc.conn, target);
}
pub fn writeSocket(self: *Node, handle: Handle, source: []const u8) HandleError!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;
}
+192
View File
@@ -0,0 +1,192 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const Node = @import("node.zig");
const Scheduler = @This();
pub const EntryPoint = *const fn(std.process.Init) anyerror!void;
const Registers = struct {
rsp: usize,
rbp: usize,
rip: usize,
rdi: usize,
};
const ContextSwitch = extern struct {
old: *Registers,
new: *Registers,
};
const State = enum {
ready,
running,
blocked,
failed,
returned,
};
const Task = struct {
regs : Registers,
stack : []align(16) u8,
entry : EntryPoint,
state : State,
wakeup: ?u64,
node : *Node,
};
gpa: Allocator,
tasks: std.ArrayList(Task),
regs: Registers,
current: ?*Task,
current_time: u64,
pub fn init(self: *Scheduler, gpa: Allocator) void {
self.gpa = gpa;
self.tasks = .empty;
self.current = null;
self.current_time = 0;
}
pub fn deinit(self: *Scheduler) void {
for (self.tasks.items) |task| {
std.heap.page_allocator.free(task.stack);
}
self.tasks.deinit(self.gpa);
}
pub fn spawn(self: *Scheduler, node: *Node, entry: EntryPoint, stack_size: usize) !void {
const stack = try std.heap.page_allocator.alignedAlloc(u8, .fromByteUnits(16), stack_size);
errdefer std.heap.page_allocator.free(stack);
var stack_top = @intFromPtr(stack.ptr) + stack.len;
stack_top &= ~@as(usize, 0xf);
stack_top -= @sizeOf(usize);
// If the entry point returns, `ret` jumps here instead of into nowhere.
@as(*usize, @ptrFromInt(stack_top)).* = @intFromPtr(&taskReturned);
const task = Task {
.regs = .{
.rsp = stack_top,
.rbp = 0,
.rip = @intFromPtr(&taskStart),
.rdi = @intFromPtr(self),
},
.stack = stack,
.entry = entry,
.state = .ready,
.wakeup = null,
.node = node,
};
try self.tasks.append(self.gpa, task);
}
fn findTaskWithState(self: *Scheduler, state: State) ?*Task {
for (self.tasks.items) |*task| {
if (task.state == state)
return task;
}
return null;
}
fn findBlockedTaskWithLowestWakeupTime(self: *Scheduler) ?*Task {
var task = self.findTaskWithState(.blocked) orelse return null;
for (self.tasks.items) |*t| {
// If the state is blocked, wakeup MUST be set
std.debug.assert(t.state != .blocked or t.wakeup != null);
if (t.state == .blocked and t.wakeup.? < task.wakeup.?) {
task = t;
}
}
return task;
}
fn advanceTimeAndUnblockTasks(self: *Scheduler, new_time: u64) void {
std.debug.assert(self.current_time < new_time);
self.current_time = new_time;
for (self.tasks.items) |*task| {
if (task.state == .blocked and task.wakeup.? <= new_time) {
task.state = .ready;
task.wakeup = null;
}
}
}
fn advanceTimeAndPickTask(self: *Scheduler) ?*Task {
const task = self.findBlockedTaskWithLowestWakeupTime() orelse return null;
self.advanceTimeAndUnblockTasks(task.wakeup.?);
return task;
}
pub fn scheduleOne(self: *Scheduler) bool {
const task = self.findTaskWithState(.ready)
orelse self.advanceTimeAndPickTask()
orelse return false;
self.current = task;
task.state = .running;
contextSwitch(&self.regs, &task.regs);
return true;
}
fn contextSwitch(old: *Registers, new: *Registers) void {
asm volatile (
\\ movq 0(%%rsi), %%rax
\\ movq 8(%%rsi), %%rcx
\\ leaq 0f(%%rip), %%rdx
\\ movq %%rsp, 0(%%rax)
\\ movq %%rbp, 8(%%rax)
\\ movq %%rdx, 16(%%rax)
\\ movq 0(%%rcx), %%rsp
\\ movq 8(%%rcx), %%rbp
\\ movq 24(%%rcx), %%rdi
\\ jmpq *16(%%rcx)
\\0:
:
: [message] "{rsi}" (&ContextSwitch{ .old = old, .new = new }),
: .{
.rax = true,
.rcx = true,
.rdx = true,
.rbx = true,
.rsi = true,
.rdi = true,
.r8 = true,
.r9 = true,
.r10 = true,
.r11 = true,
.r12 = true,
.r13 = true,
.r14 = true,
.r15 = true,
.memory = true,
});
}
fn taskStart(self: *Scheduler) callconv(.c) noreturn {
const task = self.current.?;
task.entry(task.node.processInit()) catch {
task.state = .failed;
contextSwitch(&task.regs, &self.regs);
unreachable;
};
task.state = .returned;
contextSwitch(&task.regs, &self.regs);
unreachable;
}
// Dummy return address on the task's stack. Should never be reached.
fn taskReturned() callconv(.c) noreturn {
@panic("Task returned through the fake return address");
}
// Called by the current task to return control to the scheduler
pub fn sleep(self: *Scheduler, delta_us: u64) void {
const task = self.current.?;
task.state = .blocked;
task.wakeup = self.current_time + delta_us;
contextSwitch(&task.regs, &self.regs);
}
+98
View File
@@ -0,0 +1,98 @@
const Simulator = @This();
const std = @import("std");
const Allocator = std.mem.Allocator;
const Scheduler = @import("scheduler.zig");
const Network = @import("network.zig");
const Node = @import("node.zig");
pub const EntryPoint = Scheduler.EntryPoint;
const SpawnOptions = struct {
stack_size: usize = 64 * 1024,
addresses: []const u32 = &[0]u32 {}, // TODO: How do I make an empty slice?
};
const SpawnError = error {
InvalidCommand,
NoSuchProgram,
} || Allocator.Error || std.process.Environ.CreateMapError;
const ExecutableName = struct {
name : []const u8,
entry: EntryPoint,
};
gpa: Allocator,
scheduler: Scheduler,
network: Network,
nodes: std.ArrayList(*Node),
executables: std.ArrayList(ExecutableName),
real_io: std.Io,
pub fn init(self: *Simulator, gpa: Allocator, real_io: std.Io) void {
self.gpa = gpa;
self.scheduler.init(gpa);
self.network.init(gpa);
self.nodes = .empty;
self.executables = .empty;
self.real_io = real_io;
}
pub fn deinit(self: *Simulator) void {
for (self.nodes.items) |node| {
node.deinit();
self.gpa.destroy(node);
}
self.executables.deinit(self.gpa);
self.nodes.deinit(self.gpa);
self.network.deinit();
self.scheduler.deinit();
}
pub fn addExecutable(self: *Simulator, name: []const u8, entry: EntryPoint) Allocator.Error!void {
try self.executables.append(self.gpa, ExecutableName {
.name = name,
.entry = entry,
});
}
pub fn spawn(self: *Simulator, command: []const u8, options: SpawnOptions) SpawnError!void {
const name = extractProgramNameFromCommand(command) orelse return SpawnError.InvalidCommand;
const entry = self.getExecutableEntryPoint(name) orelse return SpawnError.NoSuchProgram;
const node = try self.gpa.create(Node);
errdefer self.gpa.destroy(node);
try node.init(self.real_io, &self.scheduler, &self.network, command, options.addresses, self.gpa);
try self.nodes.append(self.gpa, node);
errdefer _ = self.nodes.swapRemove(self.nodes.items.len-1);
try self.scheduler.spawn(node, entry, options.stack_size);
}
pub fn scheduleOne(self: *Simulator) bool {
return self.scheduler.scheduleOne();
}
// Reads the first word of a command
// "program arg1 arg2 arg3" -> "program"
fn extractProgramNameFromCommand(command: []const u8) ?[]const u8 {
var cur: usize = 0;
while (cur < command.len and command[cur] != ' ')
cur += 1;
if (cur == 0)
return null;
return command[0..cur];
}
fn getExecutableEntryPoint(self: *Simulator, name: []const u8) ?EntryPoint {
for (self.executables.items) |executable| {
if (std.mem.eql(u8, executable.name, name))
return executable.entry;
}
return null;
}