First commit
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user