Commit e7729a7b89
Changed files (8)
lib/std/fs/File.zig
@@ -17,25 +17,12 @@ const Alignment = std.mem.Alignment;
/// The OS-specific file descriptor or file handle.
handle: Handle,
-pub const Handle = posix.fd_t;
-pub const Mode = posix.mode_t;
-pub const INode = posix.ino_t;
+pub const Handle = std.Io.File.Handle;
+pub const Mode = std.Io.File.Mode;
+pub const INode = std.Io.File.INode;
pub const Uid = posix.uid_t;
pub const Gid = posix.gid_t;
-
-pub const Kind = enum {
- block_device,
- character_device,
- directory,
- named_pipe,
- sym_link,
- file,
- unix_domain_socket,
- whiteout,
- door,
- event_port,
- unknown,
-};
+pub const Kind = std.Io.File.Kind;
/// This is the default mode given to POSIX operating systems for creating
/// files. `0o666` is "-rw-rw-rw-" which is counter-intuitive at first,
@@ -399,115 +386,11 @@ pub fn mode(self: File) ModeError!Mode {
return (try self.stat()).mode;
}
-pub const Stat = struct {
- /// A number that the system uses to point to the file metadata. This
- /// number is not guaranteed to be unique across time, as some file
- /// systems may reuse an inode after its file has been deleted. Some
- /// systems may change the inode of a file over time.
- ///
- /// On Linux, the inode is a structure that stores the metadata, and
- /// the inode _number_ is what you see here: the index number of the
- /// inode.
- ///
- /// The FileIndex on Windows is similar. It is a number for a file that
- /// is unique to each filesystem.
- inode: INode,
- size: u64,
- /// This is available on POSIX systems and is always 0 otherwise.
- mode: Mode,
- kind: Kind,
-
- /// Last access time in nanoseconds, relative to UTC 1970-01-01.
- atime: i128,
- /// Last modification time in nanoseconds, relative to UTC 1970-01-01.
- mtime: i128,
- /// Last status/metadata change time in nanoseconds, relative to UTC 1970-01-01.
- ctime: i128,
-
- pub fn fromPosix(st: posix.Stat) Stat {
- const atime = st.atime();
- const mtime = st.mtime();
- const ctime = st.ctime();
- return .{
- .inode = st.ino,
- .size = @bitCast(st.size),
- .mode = st.mode,
- .kind = k: {
- const m = st.mode & posix.S.IFMT;
- switch (m) {
- posix.S.IFBLK => break :k .block_device,
- posix.S.IFCHR => break :k .character_device,
- posix.S.IFDIR => break :k .directory,
- posix.S.IFIFO => break :k .named_pipe,
- posix.S.IFLNK => break :k .sym_link,
- posix.S.IFREG => break :k .file,
- posix.S.IFSOCK => break :k .unix_domain_socket,
- else => {},
- }
- if (builtin.os.tag == .illumos) switch (m) {
- posix.S.IFDOOR => break :k .door,
- posix.S.IFPORT => break :k .event_port,
- else => {},
- };
-
- break :k .unknown;
- },
- .atime = @as(i128, atime.sec) * std.time.ns_per_s + atime.nsec,
- .mtime = @as(i128, mtime.sec) * std.time.ns_per_s + mtime.nsec,
- .ctime = @as(i128, ctime.sec) * std.time.ns_per_s + ctime.nsec,
- };
- }
-
- pub fn fromLinux(stx: linux.Statx) Stat {
- const atime = stx.atime;
- const mtime = stx.mtime;
- const ctime = stx.ctime;
-
- return .{
- .inode = stx.ino,
- .size = stx.size,
- .mode = stx.mode,
- .kind = switch (stx.mode & linux.S.IFMT) {
- linux.S.IFDIR => .directory,
- linux.S.IFCHR => .character_device,
- linux.S.IFBLK => .block_device,
- linux.S.IFREG => .file,
- linux.S.IFIFO => .named_pipe,
- linux.S.IFLNK => .sym_link,
- linux.S.IFSOCK => .unix_domain_socket,
- else => .unknown,
- },
- .atime = @as(i128, atime.sec) * std.time.ns_per_s + atime.nsec,
- .mtime = @as(i128, mtime.sec) * std.time.ns_per_s + mtime.nsec,
- .ctime = @as(i128, ctime.sec) * std.time.ns_per_s + ctime.nsec,
- };
- }
-
- pub fn fromWasi(st: std.os.wasi.filestat_t) Stat {
- return .{
- .inode = st.ino,
- .size = @bitCast(st.size),
- .mode = 0,
- .kind = switch (st.filetype) {
- .BLOCK_DEVICE => .block_device,
- .CHARACTER_DEVICE => .character_device,
- .DIRECTORY => .directory,
- .SYMBOLIC_LINK => .sym_link,
- .REGULAR_FILE => .file,
- .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
- else => .unknown,
- },
- .atime = st.atim,
- .mtime = st.mtim,
- .ctime = st.ctim,
- };
- }
-};
+pub const Stat = std.Io.File.Stat;
pub const StatError = posix.FStatError;
/// Returns `Stat` containing basic information about the `File`.
-/// TODO: integrate with async I/O
pub fn stat(self: File) StatError!Stat {
if (builtin.os.tag == .windows) {
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
@@ -1728,7 +1611,7 @@ pub const Writer = struct {
pub fn sendFile(
io_w: *std.Io.Writer,
- file_reader: *Reader,
+ file_reader: *std.Io.File.Reader,
limit: std.Io.Limit,
) std.Io.Writer.FileError!usize {
const reader_buffered = file_reader.interface.buffered();
@@ -1995,7 +1878,7 @@ pub const Writer = struct {
fn sendFileBuffered(
io_w: *std.Io.Writer,
- file_reader: *Reader,
+ file_reader: *std.Io.File.Reader,
reader_buffered: []const u8,
) std.Io.Writer.FileError!usize {
const n = try drain(io_w, &.{reader_buffered}, 1);
lib/std/Io/EventLoop.zig
@@ -93,7 +93,7 @@ const Fiber = struct {
}
fn resultPointer(f: *Fiber, comptime Result: type) *Result {
- return @alignCast(@ptrCast(f.resultBytes(.of(Result))));
+ return @ptrCast(@alignCast(f.resultBytes(.of(Result))));
}
fn resultBytes(f: *Fiber, alignment: Alignment) [*]u8 {
@@ -153,8 +153,8 @@ pub fn io(el: *EventLoop) Io {
.conditionWake = conditionWake,
.createFile = createFile,
- .openFile = openFile,
- .closeFile = closeFile,
+ .fileOpen = fileOpen,
+ .fileClose = fileClose,
.pread = pread,
.pwrite = pwrite,
@@ -193,7 +193,7 @@ pub fn init(el: *EventLoop, gpa: Allocator) !void {
};
const main_thread = &el.threads.allocated[0];
Thread.self = main_thread;
- const idle_stack_end: [*]align(16) usize = @alignCast(@ptrCast(allocated_slice[idle_stack_end_offset..].ptr));
+ const idle_stack_end: [*]align(16) usize = @ptrCast(@alignCast(allocated_slice[idle_stack_end_offset..].ptr));
(idle_stack_end - 1)[0..1].* = .{@intFromPtr(el)};
main_thread.* = .{
.thread = undefined,
@@ -244,7 +244,7 @@ pub fn deinit(el: *EventLoop) void {
assert(ready_fiber == null or ready_fiber == Fiber.finished); // pending async
}
el.yield(null, .exit);
- const allocated_ptr: [*]align(@alignOf(Thread)) u8 = @alignCast(@ptrCast(el.threads.allocated.ptr));
+ const allocated_ptr: [*]align(@alignOf(Thread)) u8 = @ptrCast(@alignCast(el.threads.allocated.ptr));
const idle_stack_end_offset = std.mem.alignForward(usize, el.threads.allocated.len * @sizeOf(Thread) + idle_stack_size, std.heap.page_size_max);
for (el.threads.allocated[1..active_threads]) |*thread| thread.thread.join();
el.gpa.free(allocated_ptr[0..idle_stack_end_offset]);
@@ -530,7 +530,7 @@ const SwitchMessage = struct {
const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev));
assert(prev_fiber.queue_next == null);
for (futures) |any_future| {
- const future_fiber: *Fiber = @alignCast(@ptrCast(any_future));
+ const future_fiber: *Fiber = @ptrCast(@alignCast(any_future));
if (@atomicRmw(?*Fiber, &future_fiber.awaiter, .Xchg, prev_fiber, .acq_rel) == Fiber.finished) {
const closure: *AsyncClosure = .fromFiber(future_fiber);
if (!@atomicRmw(bool, &closure.already_awaited, .Xchg, true, .seq_cst)) {
@@ -897,12 +897,12 @@ fn asyncConcurrent(
assert(result_len <= Fiber.max_result_size); // TODO
assert(context.len <= Fiber.max_context_size); // TODO
- const event_loop: *EventLoop = @alignCast(@ptrCast(userdata));
+ const event_loop: *EventLoop = @ptrCast(@alignCast(userdata));
const fiber = try Fiber.allocate(event_loop);
std.log.debug("allocated {*}", .{fiber});
const closure: *AsyncClosure = .fromFiber(fiber);
- const stack_end: [*]align(16) usize = @alignCast(@ptrCast(closure));
+ const stack_end: [*]align(16) usize = @ptrCast(@alignCast(closure));
(stack_end - 1)[0..1].* = .{@intFromPtr(&AsyncClosure.call)};
fiber.* = .{
.required_align = {},
@@ -974,7 +974,7 @@ fn asyncDetached(
assert(context_alignment.compare(.lte, Fiber.max_context_align)); // TODO
assert(context.len <= Fiber.max_context_size); // TODO
- const event_loop: *EventLoop = @alignCast(@ptrCast(userdata));
+ const event_loop: *EventLoop = @ptrCast(@alignCast(userdata));
const fiber = Fiber.allocate(event_loop) catch {
start(context.ptr);
return;
@@ -985,7 +985,7 @@ fn asyncDetached(
const closure: *DetachedClosure = @ptrFromInt(Fiber.max_context_align.max(.of(DetachedClosure)).backward(
@intFromPtr(fiber.allocatedEnd()) - Fiber.max_context_size,
) - @sizeOf(DetachedClosure));
- const stack_end: [*]align(16) usize = @alignCast(@ptrCast(closure));
+ const stack_end: [*]align(16) usize = @ptrCast(@alignCast(closure));
(stack_end - 1)[0..1].* = .{@intFromPtr(&DetachedClosure.call)};
fiber.* = .{
.required_align = {},
@@ -1035,8 +1035,8 @@ fn await(
result: []u8,
result_alignment: Alignment,
) void {
- const event_loop: *EventLoop = @alignCast(@ptrCast(userdata));
- const future_fiber: *Fiber = @alignCast(@ptrCast(any_future));
+ const event_loop: *EventLoop = @ptrCast(@alignCast(userdata));
+ const future_fiber: *Fiber = @ptrCast(@alignCast(any_future));
if (@atomicLoad(?*Fiber, &future_fiber.awaiter, .acquire) != Fiber.finished)
event_loop.yield(null, .{ .register_awaiter = &future_fiber.awaiter });
@memcpy(result, future_fiber.resultBytes(result_alignment));
@@ -1044,11 +1044,11 @@ fn await(
}
fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize {
- const el: *EventLoop = @alignCast(@ptrCast(userdata));
+ const el: *EventLoop = @ptrCast(@alignCast(userdata));
// Optimization to avoid the yield below.
for (futures, 0..) |any_future, i| {
- const future_fiber: *Fiber = @alignCast(@ptrCast(any_future));
+ const future_fiber: *Fiber = @ptrCast(@alignCast(any_future));
if (@atomicLoad(?*Fiber, &future_fiber.awaiter, .acquire) == Fiber.finished)
return i;
}
@@ -1062,7 +1062,7 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize {
var result: ?usize = null;
for (futures, 0..) |any_future, i| {
- const future_fiber: *Fiber = @alignCast(@ptrCast(any_future));
+ const future_fiber: *Fiber = @ptrCast(@alignCast(any_future));
if (@cmpxchgStrong(?*Fiber, &future_fiber.awaiter, my_fiber, null, .seq_cst, .seq_cst)) |awaiter| {
if (awaiter == Fiber.finished) {
if (result == null) result = i;
@@ -1085,7 +1085,7 @@ fn cancel(
result: []u8,
result_alignment: Alignment,
) void {
- const future_fiber: *Fiber = @alignCast(@ptrCast(any_future));
+ const future_fiber: *Fiber = @ptrCast(@alignCast(any_future));
if (@atomicRmw(
?*Thread,
&future_fiber.cancel_thread,
@@ -1124,7 +1124,7 @@ fn createFile(
sub_path: []const u8,
flags: Io.File.CreateFlags,
) Io.File.OpenError!Io.File {
- const el: *EventLoop = @alignCast(@ptrCast(userdata));
+ const el: *EventLoop = @ptrCast(@alignCast(userdata));
const thread: *Thread = .current();
const iou = &thread.io_uring;
const fiber = thread.currentFiber();
@@ -1220,13 +1220,13 @@ fn createFile(
}
}
-fn openFile(
+fn fileOpen(
userdata: ?*anyopaque,
dir: Io.Dir,
sub_path: []const u8,
flags: Io.File.OpenFlags,
) Io.File.OpenError!Io.File {
- const el: *EventLoop = @alignCast(@ptrCast(userdata));
+ const el: *EventLoop = @ptrCast(@alignCast(userdata));
const thread: *Thread = .current();
const iou = &thread.io_uring;
const fiber = thread.currentFiber();
@@ -1328,8 +1328,8 @@ fn openFile(
}
}
-fn closeFile(userdata: ?*anyopaque, file: Io.File) void {
- const el: *EventLoop = @alignCast(@ptrCast(userdata));
+fn fileClose(userdata: ?*anyopaque, file: Io.File) void {
+ const el: *EventLoop = @ptrCast(@alignCast(userdata));
const thread: *Thread = .current();
const iou = &thread.io_uring;
const fiber = thread.currentFiber();
@@ -1365,7 +1365,7 @@ fn closeFile(userdata: ?*anyopaque, file: Io.File) void {
}
fn pread(userdata: ?*anyopaque, file: Io.File, buffer: []u8, offset: std.posix.off_t) Io.File.PReadError!usize {
- const el: *EventLoop = @alignCast(@ptrCast(userdata));
+ const el: *EventLoop = @ptrCast(@alignCast(userdata));
const thread: *Thread = .current();
const iou = &thread.io_uring;
const fiber = thread.currentFiber();
@@ -1417,7 +1417,7 @@ fn pread(userdata: ?*anyopaque, file: Io.File, buffer: []u8, offset: std.posix.o
}
fn pwrite(userdata: ?*anyopaque, file: Io.File, buffer: []const u8, offset: std.posix.off_t) Io.File.PWriteError!usize {
- const el: *EventLoop = @alignCast(@ptrCast(userdata));
+ const el: *EventLoop = @ptrCast(@alignCast(userdata));
const thread: *Thread = .current();
const iou = &thread.io_uring;
const fiber = thread.currentFiber();
@@ -1479,7 +1479,7 @@ fn now(userdata: ?*anyopaque, clockid: std.posix.clockid_t) Io.ClockGetTimeError
}
fn sleep(userdata: ?*anyopaque, clockid: std.posix.clockid_t, deadline: Io.Deadline) Io.SleepError!void {
- const el: *EventLoop = @alignCast(@ptrCast(userdata));
+ const el: *EventLoop = @ptrCast(@alignCast(userdata));
const thread: *Thread = .current();
const iou = &thread.io_uring;
const fiber = thread.currentFiber();
@@ -1532,7 +1532,7 @@ fn sleep(userdata: ?*anyopaque, clockid: std.posix.clockid_t, deadline: Io.Deadl
}
fn mutexLock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) error{Canceled}!void {
- const el: *EventLoop = @alignCast(@ptrCast(userdata));
+ const el: *EventLoop = @ptrCast(@alignCast(userdata));
el.yield(null, .{ .mutex_lock = .{ .prev_state = prev_state, .mutex = mutex } });
}
fn mutexUnlock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) void {
@@ -1553,7 +1553,7 @@ fn mutexUnlock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mut
.acquire,
) orelse return) |next_state| maybe_waiting_fiber = @ptrFromInt(@intFromEnum(next_state));
maybe_waiting_fiber.?.queue_next = null;
- const el: *EventLoop = @alignCast(@ptrCast(userdata));
+ const el: *EventLoop = @ptrCast(@alignCast(userdata));
el.yield(maybe_waiting_fiber.?, .reschedule);
}
@@ -1566,7 +1566,7 @@ const ConditionImpl = struct {
};
fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) Io.Cancelable!void {
- const el: *EventLoop = @alignCast(@ptrCast(userdata));
+ const el: *EventLoop = @ptrCast(@alignCast(userdata));
el.yield(null, .{ .condition_wait = .{ .cond = cond, .mutex = mutex } });
const thread = Thread.current();
const fiber = thread.currentFiber();
@@ -1595,7 +1595,7 @@ fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) I
}
fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.Wake) void {
- const el: *EventLoop = @alignCast(@ptrCast(userdata));
+ const el: *EventLoop = @ptrCast(@alignCast(userdata));
const waiting_fiber = @atomicRmw(?*Fiber, @as(*?*Fiber, @ptrCast(&cond.state)), .Xchg, null, .acquire) orelse return;
waiting_fiber.resultPointer(ConditionImpl).event = .{ .wake = wake };
el.yield(waiting_fiber, .reschedule);
lib/std/Io/File.zig
@@ -0,0 +1,550 @@
+const builtin = @import("builtin");
+const std = @import("../std.zig");
+const Io = std.Io;
+const File = @This();
+const assert = std.debug.assert;
+
+handle: Handle,
+
+pub const Handle = std.posix.fd_t;
+pub const Mode = std.posix.mode_t;
+pub const INode = std.posix.ino_t;
+
+pub const Kind = enum {
+ block_device,
+ character_device,
+ directory,
+ named_pipe,
+ sym_link,
+ file,
+ unix_domain_socket,
+ whiteout,
+ door,
+ event_port,
+ unknown,
+};
+
+pub const Stat = struct {
+ /// A number that the system uses to point to the file metadata. This
+ /// number is not guaranteed to be unique across time, as some file
+ /// systems may reuse an inode after its file has been deleted. Some
+ /// systems may change the inode of a file over time.
+ ///
+ /// On Linux, the inode is a structure that stores the metadata, and
+ /// the inode _number_ is what you see here: the index number of the
+ /// inode.
+ ///
+ /// The FileIndex on Windows is similar. It is a number for a file that
+ /// is unique to each filesystem.
+ inode: INode,
+ size: u64,
+ /// This is available on POSIX systems and is always 0 otherwise.
+ mode: Mode,
+ kind: Kind,
+
+ /// Last access time in nanoseconds, relative to UTC 1970-01-01.
+ atime: i128,
+ /// Last modification time in nanoseconds, relative to UTC 1970-01-01.
+ mtime: i128,
+ /// Last status/metadata change time in nanoseconds, relative to UTC 1970-01-01.
+ ctime: i128,
+
+ pub fn fromPosix(st: std.posix.Stat) Stat {
+ const atime = st.atime();
+ const mtime = st.mtime();
+ const ctime = st.ctime();
+ return .{
+ .inode = st.ino,
+ .size = @bitCast(st.size),
+ .mode = st.mode,
+ .kind = k: {
+ const m = st.mode & std.posix.S.IFMT;
+ switch (m) {
+ std.posix.S.IFBLK => break :k .block_device,
+ std.posix.S.IFCHR => break :k .character_device,
+ std.posix.S.IFDIR => break :k .directory,
+ std.posix.S.IFIFO => break :k .named_pipe,
+ std.posix.S.IFLNK => break :k .sym_link,
+ std.posix.S.IFREG => break :k .file,
+ std.posix.S.IFSOCK => break :k .unix_domain_socket,
+ else => {},
+ }
+ if (builtin.os.tag == .illumos) switch (m) {
+ std.posix.S.IFDOOR => break :k .door,
+ std.posix.S.IFPORT => break :k .event_port,
+ else => {},
+ };
+
+ break :k .unknown;
+ },
+ .atime = @as(i128, atime.sec) * std.time.ns_per_s + atime.nsec,
+ .mtime = @as(i128, mtime.sec) * std.time.ns_per_s + mtime.nsec,
+ .ctime = @as(i128, ctime.sec) * std.time.ns_per_s + ctime.nsec,
+ };
+ }
+
+ pub fn fromLinux(stx: std.os.linux.Statx) Stat {
+ const atime = stx.atime;
+ const mtime = stx.mtime;
+ const ctime = stx.ctime;
+
+ return .{
+ .inode = stx.ino,
+ .size = stx.size,
+ .mode = stx.mode,
+ .kind = switch (stx.mode & std.os.linux.S.IFMT) {
+ std.os.linux.S.IFDIR => .directory,
+ std.os.linux.S.IFCHR => .character_device,
+ std.os.linux.S.IFBLK => .block_device,
+ std.os.linux.S.IFREG => .file,
+ std.os.linux.S.IFIFO => .named_pipe,
+ std.os.linux.S.IFLNK => .sym_link,
+ std.os.linux.S.IFSOCK => .unix_domain_socket,
+ else => .unknown,
+ },
+ .atime = @as(i128, atime.sec) * std.time.ns_per_s + atime.nsec,
+ .mtime = @as(i128, mtime.sec) * std.time.ns_per_s + mtime.nsec,
+ .ctime = @as(i128, ctime.sec) * std.time.ns_per_s + ctime.nsec,
+ };
+ }
+
+ pub fn fromWasi(st: std.os.wasi.filestat_t) Stat {
+ return .{
+ .inode = st.ino,
+ .size = @bitCast(st.size),
+ .mode = 0,
+ .kind = switch (st.filetype) {
+ .BLOCK_DEVICE => .block_device,
+ .CHARACTER_DEVICE => .character_device,
+ .DIRECTORY => .directory,
+ .SYMBOLIC_LINK => .sym_link,
+ .REGULAR_FILE => .file,
+ .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
+ else => .unknown,
+ },
+ .atime = st.atim,
+ .mtime = st.mtim,
+ .ctime = st.ctim,
+ };
+ }
+};
+
+pub const StatError = std.posix.FStatError || Io.Cancelable;
+
+/// Returns `Stat` containing basic information about the `File`.
+pub fn stat(file: File, io: Io) StatError!Stat {
+ _ = file;
+ _ = io;
+ @panic("TODO");
+}
+
+pub const OpenFlags = std.fs.File.OpenFlags;
+pub const CreateFlags = std.fs.File.CreateFlags;
+
+pub const OpenError = std.fs.File.OpenError || Io.Cancelable;
+
+pub fn close(file: File, io: Io) void {
+ return io.vtable.fileClose(io.userdata, file);
+}
+
+pub const ReadStreamingError = error{
+ InputOutput,
+ SystemResources,
+ IsDir,
+ BrokenPipe,
+ ConnectionResetByPeer,
+ ConnectionTimedOut,
+ NotOpenForReading,
+ SocketNotConnected,
+ /// This error occurs when no global event loop is configured,
+ /// and reading from the file descriptor would block.
+ WouldBlock,
+ /// In WASI, this error occurs when the file descriptor does
+ /// not hold the required rights to read from it.
+ AccessDenied,
+ /// This error occurs in Linux if the process to be read from
+ /// no longer exists.
+ ProcessNotFound,
+ /// Unable to read file due to lock.
+ LockViolation,
+} || Io.Cancelable || Io.UnexpectedError;
+
+pub const ReadPositionalError = ReadStreamingError || error{Unseekable};
+
+pub fn readPositional(file: File, io: Io, buffer: []u8, offset: u64) ReadPositionalError!usize {
+ return io.vtable.pread(io.userdata, file, buffer, offset);
+}
+
+pub const WriteError = std.fs.File.WriteError || Io.Cancelable;
+
+pub fn write(file: File, io: Io, buffer: []const u8) WriteError!usize {
+ return @errorCast(file.pwrite(io, buffer, -1));
+}
+
+pub const PWriteError = std.fs.File.PWriteError || Io.Cancelable;
+
+pub fn pwrite(file: File, io: Io, buffer: []const u8, offset: std.posix.off_t) PWriteError!usize {
+ return io.vtable.pwrite(io.userdata, file, buffer, offset);
+}
+
+pub fn openAbsolute(io: Io, absolute_path: []const u8, flags: OpenFlags) OpenError!File {
+ assert(std.fs.path.isAbsolute(absolute_path));
+ return Io.Dir.cwd().openFile(io, absolute_path, flags);
+}
+
+/// Defaults to positional reading; falls back to streaming.
+///
+/// Positional is more threadsafe, since the global seek position is not
+/// affected.
+pub fn reader(file: File, io: Io, buffer: []u8) Reader {
+ return .init(file, io, buffer);
+}
+
+/// Positional is more threadsafe, since the global seek position is not
+/// affected, but when such syscalls are not available, preemptively
+/// initializing in streaming mode skips a failed syscall.
+pub fn readerStreaming(file: File, io: Io, buffer: []u8) Reader {
+ return .initStreaming(file, io, buffer);
+}
+
+pub const SeekError = error{
+ Unseekable,
+ /// The file descriptor does not hold the required rights to seek on it.
+ AccessDenied,
+} || Io.Cancelable || Io.UnexpectedError;
+
+/// Memoizes key information about a file handle such as:
+/// * The size from calling stat, or the error that occurred therein.
+/// * The current seek position.
+/// * The error that occurred when trying to seek.
+/// * Whether reading should be done positionally or streaming.
+/// * Whether reading should be done via fd-to-fd syscalls (e.g. `sendfile`)
+/// versus plain variants (e.g. `read`).
+///
+/// Fulfills the `Io.Reader` interface.
+pub const Reader = struct {
+ io: Io,
+ file: File,
+ err: ?Error = null,
+ mode: Reader.Mode = .positional,
+ /// Tracks the true seek position in the file. To obtain the logical
+ /// position, use `logicalPos`.
+ pos: u64 = 0,
+ size: ?u64 = null,
+ size_err: ?SizeError = null,
+ seek_err: ?Reader.SeekError = null,
+ interface: Io.Reader,
+
+ pub const Error = std.posix.ReadError || Io.Cancelable;
+
+ pub const SizeError = std.os.windows.GetFileSizeError || StatError || error{
+ /// Occurs if, for example, the file handle is a network socket and therefore does not have a size.
+ Streaming,
+ };
+
+ pub const SeekError = File.SeekError || error{
+ /// Seeking fell back to reading, and reached the end before the requested seek position.
+ /// `pos` remains at the end of the file.
+ EndOfStream,
+ /// Seeking fell back to reading, which failed.
+ ReadFailed,
+ };
+
+ pub const Mode = enum {
+ streaming,
+ positional,
+ /// Avoid syscalls other than `read` and `readv`.
+ streaming_reading,
+ /// Avoid syscalls other than `pread` and `preadv`.
+ positional_reading,
+ /// Indicates reading cannot continue because of a seek failure.
+ failure,
+
+ pub fn toStreaming(m: @This()) @This() {
+ return switch (m) {
+ .positional, .streaming => .streaming,
+ .positional_reading, .streaming_reading => .streaming_reading,
+ .failure => .failure,
+ };
+ }
+
+ pub fn toReading(m: @This()) @This() {
+ return switch (m) {
+ .positional, .positional_reading => .positional_reading,
+ .streaming, .streaming_reading => .streaming_reading,
+ .failure => .failure,
+ };
+ }
+ };
+
+ pub fn initInterface(buffer: []u8) Io.Reader {
+ return .{
+ .vtable = &.{
+ .stream = Reader.stream,
+ .discard = Reader.discard,
+ .readVec = Reader.readVec,
+ },
+ .buffer = buffer,
+ .seek = 0,
+ .end = 0,
+ };
+ }
+
+ pub fn init(file: File, io: Io, buffer: []u8) Reader {
+ return .{
+ .io = io,
+ .file = file,
+ .interface = initInterface(buffer),
+ };
+ }
+
+ pub fn initSize(file: File, io: Io, buffer: []u8, size: ?u64) Reader {
+ return .{
+ .io = io,
+ .file = file,
+ .interface = initInterface(buffer),
+ .size = size,
+ };
+ }
+
+ /// Positional is more threadsafe, since the global seek position is not
+ /// affected, but when such syscalls are not available, preemptively
+ /// initializing in streaming mode skips a failed syscall.
+ pub fn initStreaming(file: File, io: Io, buffer: []u8) Reader {
+ return .{
+ .io = io,
+ .file = file,
+ .interface = Reader.initInterface(buffer),
+ .mode = .streaming,
+ .seek_err = error.Unseekable,
+ .size_err = error.Streaming,
+ };
+ }
+
+ pub fn getSize(r: *Reader) SizeError!u64 {
+ return r.size orelse {
+ if (r.size_err) |err| return err;
+ if (std.posix.Stat == void) {
+ r.size_err = error.Streaming;
+ return error.Streaming;
+ }
+ if (stat(r.file, r.io)) |st| {
+ if (st.kind == .file) {
+ r.size = st.size;
+ return st.size;
+ } else {
+ r.mode = r.mode.toStreaming();
+ r.size_err = error.Streaming;
+ return error.Streaming;
+ }
+ } else |err| {
+ r.size_err = err;
+ return err;
+ }
+ };
+ }
+
+ pub fn seekBy(r: *Reader, offset: i64) Reader.SeekError!void {
+ const io = r.io;
+ switch (r.mode) {
+ .positional, .positional_reading => {
+ setPosAdjustingBuffer(r, @intCast(@as(i64, @intCast(r.pos)) + offset));
+ },
+ .streaming, .streaming_reading => {
+ if (std.posix.SEEK == void) {
+ r.seek_err = error.Unseekable;
+ return error.Unseekable;
+ }
+ const seek_err = r.seek_err orelse e: {
+ if (io.vtable.fileSeekBy(io.userdata, r.file, offset)) |_| {
+ setPosAdjustingBuffer(r, @intCast(@as(i64, @intCast(r.pos)) + offset));
+ return;
+ } else |err| {
+ r.seek_err = err;
+ break :e err;
+ }
+ };
+ var remaining = std.math.cast(u64, offset) orelse return seek_err;
+ while (remaining > 0) {
+ remaining -= discard(&r.interface, .limited64(remaining)) catch |err| {
+ r.seek_err = err;
+ return err;
+ };
+ }
+ r.interface.seek = 0;
+ r.interface.end = 0;
+ },
+ .failure => return r.seek_err.?,
+ }
+ }
+
+ pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void {
+ const io = r.io;
+ switch (r.mode) {
+ .positional, .positional_reading => {
+ setPosAdjustingBuffer(r, offset);
+ },
+ .streaming, .streaming_reading => {
+ if (offset >= r.pos) return Reader.seekBy(r, @intCast(offset - r.pos));
+ if (r.seek_err) |err| return err;
+ io.vtable.fileSeekTo(io.userdata, r.file, offset) catch |err| {
+ r.seek_err = err;
+ return err;
+ };
+ setPosAdjustingBuffer(r, offset);
+ },
+ .failure => return r.seek_err.?,
+ }
+ }
+
+ pub fn logicalPos(r: *const Reader) u64 {
+ return r.pos - r.interface.bufferedLen();
+ }
+
+ fn setPosAdjustingBuffer(r: *Reader, offset: u64) void {
+ const logical_pos = logicalPos(r);
+ if (offset < logical_pos or offset >= r.pos) {
+ r.interface.seek = 0;
+ r.interface.end = 0;
+ r.pos = offset;
+ } else {
+ const logical_delta: usize = @intCast(offset - logical_pos);
+ r.interface.seek += logical_delta;
+ }
+ }
+
+ /// Number of slices to store on the stack, when trying to send as many byte
+ /// vectors through the underlying read calls as possible.
+ const max_buffers_len = 16;
+
+ fn stream(io_reader: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
+ const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
+ switch (r.mode) {
+ .positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) {
+ error.Unimplemented => {
+ r.mode = r.mode.toReading();
+ return 0;
+ },
+ else => |e| return e,
+ },
+ .positional_reading => {
+ const dest = limit.slice(try w.writableSliceGreedy(1));
+ var data: [1][]u8 = .{dest};
+ const n = try readVecPositional(r, &data);
+ w.advance(n);
+ return n;
+ },
+ .streaming_reading => {
+ const dest = limit.slice(try w.writableSliceGreedy(1));
+ var data: [1][]u8 = .{dest};
+ const n = try readVecStreaming(r, &data);
+ w.advance(n);
+ return n;
+ },
+ .failure => return error.ReadFailed,
+ }
+ }
+
+ fn readVec(io_reader: *Io.Reader, data: [][]u8) Io.Reader.Error!usize {
+ const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
+ switch (r.mode) {
+ .positional, .positional_reading => return readVecPositional(r, data),
+ .streaming, .streaming_reading => return readVecStreaming(r, data),
+ .failure => return error.ReadFailed,
+ }
+ }
+
+ fn readVecPositional(r: *Reader, data: [][]u8) Io.Reader.Error!usize {
+ const io = r.io;
+ assert(r.interface.bufferedLen() == 0);
+ var iovecs_buffer: [max_buffers_len][]u8 = undefined;
+ const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data);
+ const dest = iovecs_buffer[0..dest_n];
+ assert(dest[0].len > 0);
+ const n = io.vtable.fileReadPositional(io.userdata, r.file, dest, r.pos) catch |err| switch (err) {
+ error.Unseekable => {
+ r.mode = r.mode.toStreaming();
+ const pos = r.pos;
+ if (pos != 0) {
+ r.pos = 0;
+ r.seekBy(@intCast(pos)) catch {
+ r.mode = .failure;
+ return error.ReadFailed;
+ };
+ }
+ return 0;
+ },
+ else => |e| {
+ r.err = e;
+ return error.ReadFailed;
+ },
+ };
+ if (n == 0) {
+ r.size = r.pos;
+ return error.EndOfStream;
+ }
+ r.pos += n;
+ if (n > data_size) {
+ r.interface.end += n - data_size;
+ return data_size;
+ }
+ return n;
+ }
+
+ fn readVecStreaming(r: *Reader, data: [][]u8) Io.Reader.Error!usize {
+ const io = r.io;
+ var iovecs_buffer: [max_buffers_len][]u8 = undefined;
+ const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data);
+ const dest = iovecs_buffer[0..dest_n];
+ assert(dest[0].len > 0);
+ const n = io.vtable.fileReadStreaming(io.userdata, r.file, dest) catch |err| {
+ r.err = err;
+ return error.ReadFailed;
+ };
+ if (n == 0) {
+ r.size = r.pos;
+ return error.EndOfStream;
+ }
+ r.pos += n;
+ if (n > data_size) {
+ r.interface.end += n - data_size;
+ return data_size;
+ }
+ return n;
+ }
+
+ fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
+ const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
+ const io = r.io;
+ const file = r.file;
+ const pos = r.pos;
+ switch (r.mode) {
+ .positional, .positional_reading => {
+ const size = r.getSize() catch {
+ r.mode = r.mode.toStreaming();
+ return 0;
+ };
+ const delta = @min(@intFromEnum(limit), size - pos);
+ r.pos = pos + delta;
+ return delta;
+ },
+ .streaming, .streaming_reading => {
+ const size = r.getSize() catch return 0;
+ const n = @min(size - pos, std.math.maxInt(i64), @intFromEnum(limit));
+ io.vtable.fileSeekBy(io.userdata, file, n) catch |err| {
+ r.seek_err = err;
+ return 0;
+ };
+ r.pos = pos + n;
+ return n;
+ },
+ .failure => return error.ReadFailed,
+ }
+ }
+
+ pub fn atEnd(r: *Reader) bool {
+ // Even if stat fails, size is set when end is encountered.
+ const size = r.size orelse return false;
+ return size - r.pos == 0;
+ }
+};
lib/std/Io/net.zig
@@ -17,9 +17,12 @@ pub const ListenOptions = struct {
force_nonblocking: bool = false,
};
-/// An already-validated host name.
+/// An already-validated host name. A valid host name:
+/// * Has length less than or equal to `max_len`.
+/// * Is valid UTF-8.
+/// * Lacks ASCII characters other than alphanumeric, '-', and '.'.
pub const HostName = struct {
- /// Externally managed memory. Already checked to be within `max_len`.
+ /// Externally managed memory. Already checked to be valid.
bytes: []const u8,
pub const max_len = 255;
@@ -55,13 +58,14 @@ pub const HostName = struct {
family: ?IpAddress.Tag = null,
};
- pub const LookupError = Io.Cancelable || error{};
+ pub const LookupError = Io.Cancelable || Io.File.OpenError || Io.File.Reader.Error || error{
+ UnknownHostName,
+ };
pub const LookupResult = struct {
/// How many `LookupOptions.addresses_buffer` elements are populated.
- addresses_len: usize,
- /// Length zero means no canonical name returned.
- canonical_name_len: usize,
+ addresses_len: usize = 0,
+ canonical_name: ?HostName = null,
};
pub fn lookup(host_name: HostName, io: Io, options: LookupOptions) LookupError!LookupResult {
@@ -75,17 +79,17 @@ pub const HostName = struct {
if (options.family != .ip6) {
if (IpAddress.parseIp4(name, options.port)) |addr| {
options.addresses_buffer[0] = addr;
- return .{ .addresses_len = 1, .canonical_name_len = 0 };
+ return .{ .addresses_len = 1 };
} else |_| {}
}
if (options.family != .ip4) {
if (IpAddress.parseIp6(name, options.port)) |addr| {
options.addresses_buffer[0] = addr;
- return .{ .addresses_len = 1, .canonical_name_len = 0 };
+ return .{ .addresses_len = 1 };
} else |_| {}
}
{
- const result = try lookupHosts(io, options);
+ const result = try lookupHosts(host_name, io, options);
if (result.addresses_len > 0) return sortLookupResults(options, result);
}
{
@@ -110,8 +114,12 @@ pub const HostName = struct {
i += 1;
}
const canon_name = "localhost";
- options.canonical_name_buffer[0..canon_name.len].* = canon_name.*;
- return sortLookupResults(options, .{ .addresses_len = i, .canonical_name_len = canon_name.len });
+ const canon_name_dest = options.canonical_name_buffer[0..canon_name.len];
+ canon_name_dest.* = canon_name.*;
+ return sortLookupResults(options, .{
+ .addresses_len = i,
+ .canonical_name = .{ .bytes = canon_name_dest },
+ });
}
}
{
@@ -135,27 +143,27 @@ pub const HostName = struct {
@panic("TODO");
}
- fn lookupHosts(io: Io, options: LookupOptions) !LookupResult {
- const file = Io.File.openFileAbsoluteZ(io, "/etc/hosts", .{}) catch |err| switch (err) {
+ fn lookupHosts(host_name: HostName, io: Io, options: LookupOptions) !LookupResult {
+ const file = Io.File.openAbsolute(io, "/etc/hosts", .{}) catch |err| switch (err) {
error.FileNotFound,
error.NotDir,
error.AccessDenied,
- => return,
+ => return .{},
+
else => |e| return e,
};
- defer file.close();
+ defer file.close(io);
var line_buf: [512]u8 = undefined;
var file_reader = file.reader(io, &line_buf);
- return lookupHostsReader(options, &file_reader.interface) catch |err| switch (err) {
- error.OutOfMemory => return error.OutOfMemory,
+ return lookupHostsReader(host_name, options, &file_reader.interface) catch |err| switch (err) {
error.ReadFailed => return file_reader.err.?,
};
}
- fn lookupHostsReader(options: LookupOptions, reader: *Io.Reader) error{ReadFailed}!LookupResult {
+ fn lookupHostsReader(host_name: HostName, options: LookupOptions, reader: *Io.Reader) error{ReadFailed}!LookupResult {
var addresses_len: usize = 0;
- var canonical_name_len: usize = 0;
+ var canonical_name: ?HostName = null;
while (true) {
const line = reader.takeDelimiterExclusive('\n') catch |err| switch (err) {
error.StreamTooLong => {
@@ -176,19 +184,20 @@ pub const HostName = struct {
const ip_text = line_it.next() orelse continue;
var first_name_text: ?[]const u8 = null;
while (line_it.next()) |name_text| {
- if (std.mem.eql(u8, name_text, options.name)) {
+ if (std.mem.eql(u8, name_text, host_name.bytes)) {
if (first_name_text == null) first_name_text = name_text;
break;
}
} else continue;
- if (canonical_name_len == 0) {
- if (HostName.init(first_name_text)) |name_text| {
- if (name_text.len <= options.canonical_name_buffer.len) {
- @memcpy(options.canonical_name_buffer[0..name_text.len], name_text);
- canonical_name_len = name_text.len;
+ if (canonical_name == null) {
+ if (HostName.init(first_name_text.?)) |name_text| {
+ if (name_text.bytes.len <= options.canonical_name_buffer.len) {
+ const canonical_name_dest = options.canonical_name_buffer[0..name_text.bytes.len];
+ @memcpy(canonical_name_dest, name_text.bytes);
+ canonical_name = .{ .bytes = canonical_name_dest };
}
- }
+ } else |_| {}
}
if (options.family != .ip6) {
@@ -197,7 +206,7 @@ pub const HostName = struct {
addresses_len += 1;
if (options.addresses_buffer.len - addresses_len == 0) return .{
.addresses_len = addresses_len,
- .canonical_name_len = canonical_name_len,
+ .canonical_name = canonical_name,
};
} else |_| {}
}
@@ -207,11 +216,15 @@ pub const HostName = struct {
addresses_len += 1;
if (options.addresses_buffer.len - addresses_len == 0) return .{
.addresses_len = addresses_len,
- .canonical_name_len = canonical_name_len,
+ .canonical_name = canonical_name,
};
} else |_| {}
}
}
+ return .{
+ .addresses_len = addresses_len,
+ .canonical_name = canonical_name,
+ };
}
pub const ConnectTcpError = LookupError || IpAddress.ConnectTcpError;
@@ -289,9 +302,9 @@ pub const IpAddress = union(enum) {
}
}
- pub fn format(a: IpAddress, w: *std.io.Writer) std.io.Writer.Error!void {
+ pub fn format(a: IpAddress, w: *Io.Writer) Io.Writer.Error!void {
switch (a) {
- .ip4, .ip6 => |x| return x.format(w),
+ inline .ip4, .ip6 => |x| return x.format(w),
}
}
@@ -365,7 +378,7 @@ pub const Ip4Address = struct {
return error.Incomplete;
}
- pub fn format(a: Ip4Address, w: *std.io.Writer) std.io.Writer.Error!void {
+ pub fn format(a: Ip4Address, w: *Io.Writer) Io.Writer.Error!void {
const bytes = &a.bytes;
try w.print("{d}.{d}.{d}.{d}:{d}", .{ bytes[0], bytes[1], bytes[2], bytes[3], a.port });
}
@@ -393,6 +406,13 @@ pub const Ip6Address = struct {
Incomplete,
};
+ pub fn localhost(port: u16) Ip6Address {
+ return .{
+ .bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
+ .port = port,
+ };
+ }
+
pub fn parse(buffer: []const u8, port: u16) ParseError!Ip6Address {
var result: Ip6Address = .{
.port = port,
@@ -504,7 +524,7 @@ pub const Ip6Address = struct {
}
}
- pub fn format(a: Ip6Address, w: *std.io.Writer) std.io.Writer.Error!void {
+ pub fn format(a: Ip6Address, w: *Io.Writer) Io.Writer.Error!void {
const bytes = &a.bytes;
if (std.mem.eql(u8, bytes[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) {
try w.print("[::ffff:{d}.{d}.{d}.{d}]:{d}", .{
lib/std/Io/ThreadPool.zig
@@ -1,11 +1,16 @@
+const Pool = @This();
+
const builtin = @import("builtin");
+const native_os = builtin.os.tag;
+const is_windows = native_os == .windows;
+const windows = std.os.windows;
+
const std = @import("../std.zig");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const WaitGroup = std.Thread.WaitGroup;
const posix = std.posix;
const Io = std.Io;
-const Pool = @This();
/// Thread-safe.
allocator: Allocator,
@@ -23,6 +28,10 @@ threadlocal var current_closure: ?*AsyncClosure = null;
const max_iovecs_len = 8;
const splat_buffer_size = 64;
+comptime {
+ assert(max_iovecs_len <= posix.IOV_MAX);
+}
+
pub const Runnable = struct {
start: Start,
node: std.SinglyLinkedList.Node = .{},
@@ -104,10 +113,13 @@ pub fn io(pool: *Pool) Io {
.conditionWake = conditionWake,
.createFile = createFile,
- .openFile = openFile,
- .closeFile = closeFile,
- .pread = pread,
+ .fileOpen = fileOpen,
+ .fileClose = fileClose,
.pwrite = pwrite,
+ .fileReadStreaming = fileReadStreaming,
+ .fileReadPositional = fileReadPositional,
+ .fileSeekBy = fileSeekBy,
+ .fileSeekTo = fileSeekTo,
.now = now,
.sleep = sleep,
@@ -631,7 +643,7 @@ fn createFile(
return .{ .handle = fs_file.handle };
}
-fn openFile(
+fn fileOpen(
userdata: ?*anyopaque,
dir: Io.Dir,
sub_path: []const u8,
@@ -644,21 +656,256 @@ fn openFile(
return .{ .handle = fs_file.handle };
}
-fn closeFile(userdata: ?*anyopaque, file: Io.File) void {
+fn fileClose(userdata: ?*anyopaque, file: Io.File) void {
const pool: *Pool = @ptrCast(@alignCast(userdata));
_ = pool;
const fs_file: std.fs.File = .{ .handle = file.handle };
return fs_file.close();
}
-fn pread(userdata: ?*anyopaque, file: Io.File, buffer: []u8, offset: posix.off_t) Io.File.PReadError!usize {
+fn fileReadStreaming(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File.ReadStreamingError!usize {
const pool: *Pool = @ptrCast(@alignCast(userdata));
- try pool.checkCancel();
- const fs_file: std.fs.File = .{ .handle = file.handle };
- return switch (offset) {
- -1 => fs_file.read(buffer),
- else => fs_file.pread(buffer, @bitCast(offset)),
+
+ if (is_windows) {
+ const DWORD = windows.DWORD;
+ var index: usize = 0;
+ var truncate: usize = 0;
+ var total: usize = 0;
+ while (index < data.len) {
+ try pool.checkCancel();
+ {
+ const untruncated = data[index];
+ data[index] = untruncated[truncate..];
+ defer data[index] = untruncated;
+ const buffer = data[index..];
+ const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len);
+ var n: DWORD = undefined;
+ if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) == 0) {
+ switch (windows.GetLastError()) {
+ .IO_PENDING => unreachable,
+ .OPERATION_ABORTED => continue,
+ .BROKEN_PIPE => return 0,
+ .HANDLE_EOF => return 0,
+ .NETNAME_DELETED => return error.ConnectionResetByPeer,
+ .LOCK_VIOLATION => return error.LockViolation,
+ .ACCESS_DENIED => return error.AccessDenied,
+ .INVALID_HANDLE => return error.NotOpenForReading,
+ else => |err| return windows.unexpectedError(err),
+ }
+ }
+ total += n;
+ truncate += n;
+ }
+ while (index < data.len and truncate >= data[index].len) {
+ truncate -= data[index].len;
+ index += 1;
+ }
+ }
+ return total;
+ }
+
+ var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
+ var i: usize = 0;
+ for (data) |buf| {
+ if (iovecs_buffer.len - i == 0) break;
+ if (buf.len != 0) {
+ iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len };
+ i += 1;
+ }
+ }
+ const dest = iovecs_buffer[0..i];
+ assert(dest[0].len > 0);
+
+ if (native_os == .wasi and !builtin.link_libc) {
+ try pool.checkCancel();
+ var nread: usize = undefined;
+ switch (std.os.wasi.fd_read(file.handle, dest.ptr, dest.len, &nread)) {
+ .SUCCESS => return nread,
+ .INTR => unreachable,
+ .INVAL => unreachable,
+ .FAULT => unreachable,
+ .AGAIN => unreachable, // currently not support in WASI
+ .BADF => return error.NotOpenForReading, // can be a race condition
+ .IO => return error.InputOutput,
+ .ISDIR => return error.IsDir,
+ .NOBUFS => return error.SystemResources,
+ .NOMEM => return error.SystemResources,
+ .NOTCONN => return error.SocketNotConnected,
+ .CONNRESET => return error.ConnectionResetByPeer,
+ .TIMEDOUT => return error.ConnectionTimedOut,
+ .NOTCAPABLE => return error.AccessDenied,
+ else => |err| return posix.unexpectedErrno(err),
+ }
+ }
+
+ while (true) {
+ try pool.checkCancel();
+ const rc = posix.system.readv(file.handle, dest.ptr, dest.len);
+ switch (posix.errno(rc)) {
+ .SUCCESS => return @intCast(rc),
+ .INTR => continue,
+ .INVAL => unreachable,
+ .FAULT => unreachable,
+ .SRCH => return error.ProcessNotFound,
+ .AGAIN => return error.WouldBlock,
+ .BADF => return error.NotOpenForReading, // can be a race condition
+ .IO => return error.InputOutput,
+ .ISDIR => return error.IsDir,
+ .NOBUFS => return error.SystemResources,
+ .NOMEM => return error.SystemResources,
+ .NOTCONN => return error.SocketNotConnected,
+ .CONNRESET => return error.ConnectionResetByPeer,
+ .TIMEDOUT => return error.ConnectionTimedOut,
+ else => |err| return posix.unexpectedErrno(err),
+ }
+ }
+}
+
+fn fileReadPositional(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset: u64) Io.File.ReadPositionalError!usize {
+ const pool: *Pool = @ptrCast(@alignCast(userdata));
+
+ const have_pread_but_not_preadv = switch (native_os) {
+ .windows, .macos, .ios, .watchos, .tvos, .visionos, .haiku, .serenity => true,
+ else => false,
};
+ if (have_pread_but_not_preadv) {
+ @compileError("TODO");
+ }
+
+ if (is_windows) {
+ const DWORD = windows.DWORD;
+ const OVERLAPPED = windows.OVERLAPPED;
+ var index: usize = 0;
+ var truncate: usize = 0;
+ var total: usize = 0;
+ while (true) {
+ try pool.checkCancel();
+ {
+ const untruncated = data[index];
+ data[index] = untruncated[truncate..];
+ defer data[index] = untruncated;
+ const buffer = data[index..];
+ const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len);
+ var n: DWORD = undefined;
+ var overlapped_data: OVERLAPPED = undefined;
+ const overlapped: ?*OVERLAPPED = if (offset) |off| blk: {
+ overlapped_data = .{
+ .Internal = 0,
+ .InternalHigh = 0,
+ .DUMMYUNIONNAME = .{
+ .DUMMYSTRUCTNAME = .{
+ .Offset = @as(u32, @truncate(off)),
+ .OffsetHigh = @as(u32, @truncate(off >> 32)),
+ },
+ },
+ .hEvent = null,
+ };
+ break :blk &overlapped_data;
+ } else null;
+ if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, overlapped) == 0) {
+ switch (windows.GetLastError()) {
+ .IO_PENDING => unreachable,
+ .OPERATION_ABORTED => continue,
+ .BROKEN_PIPE => return 0,
+ .HANDLE_EOF => return 0,
+ .NETNAME_DELETED => return error.ConnectionResetByPeer,
+ .LOCK_VIOLATION => return error.LockViolation,
+ .ACCESS_DENIED => return error.AccessDenied,
+ .INVALID_HANDLE => return error.NotOpenForReading,
+ else => |err| return windows.unexpectedError(err),
+ }
+ }
+ total += n;
+ truncate += n;
+ }
+ while (index < data.len and truncate >= data[index].len) {
+ truncate -= data[index].len;
+ index += 1;
+ }
+ }
+ return total;
+ }
+
+ var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
+ var i: usize = 0;
+ for (data) |buf| {
+ if (iovecs_buffer.len - i == 0) break;
+ if (buf.len != 0) {
+ iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len };
+ i += 1;
+ }
+ }
+ const dest = iovecs_buffer[0..i];
+ assert(dest[0].len > 0);
+
+ if (native_os == .wasi and !builtin.link_libc) {
+ try pool.checkCancel();
+ var nread: usize = undefined;
+ switch (std.os.wasi.fd_pread(file.handle, dest.ptr, dest.len, offset, &nread)) {
+ .SUCCESS => return nread,
+ .INTR => unreachable,
+ .INVAL => unreachable,
+ .FAULT => unreachable,
+ .AGAIN => unreachable,
+ .BADF => return error.NotOpenForReading, // can be a race condition
+ .IO => return error.InputOutput,
+ .ISDIR => return error.IsDir,
+ .NOBUFS => return error.SystemResources,
+ .NOMEM => return error.SystemResources,
+ .NOTCONN => return error.SocketNotConnected,
+ .CONNRESET => return error.ConnectionResetByPeer,
+ .TIMEDOUT => return error.ConnectionTimedOut,
+ .NXIO => return error.Unseekable,
+ .SPIPE => return error.Unseekable,
+ .OVERFLOW => return error.Unseekable,
+ .NOTCAPABLE => return error.AccessDenied,
+ else => |err| return posix.unexpectedErrno(err),
+ }
+ }
+
+ const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv;
+ while (true) {
+ try pool.checkCancel();
+ const rc = preadv_sym(file.handle, dest.ptr, dest.len, @bitCast(offset));
+ switch (posix.errno(rc)) {
+ .SUCCESS => return @bitCast(rc),
+ .INTR => continue,
+ .INVAL => unreachable,
+ .FAULT => unreachable,
+ .SRCH => return error.ProcessNotFound,
+ .AGAIN => return error.WouldBlock,
+ .BADF => return error.NotOpenForReading, // can be a race condition
+ .IO => return error.InputOutput,
+ .ISDIR => return error.IsDir,
+ .NOBUFS => return error.SystemResources,
+ .NOMEM => return error.SystemResources,
+ .NOTCONN => return error.SocketNotConnected,
+ .CONNRESET => return error.ConnectionResetByPeer,
+ .TIMEDOUT => return error.ConnectionTimedOut,
+ .NXIO => return error.Unseekable,
+ .SPIPE => return error.Unseekable,
+ .OVERFLOW => return error.Unseekable,
+ else => |err| return posix.unexpectedErrno(err),
+ }
+ }
+}
+
+fn fileSeekBy(userdata: ?*anyopaque, file: Io.File, offset: i64) Io.File.SeekError!void {
+ const pool: *Pool = @ptrCast(@alignCast(userdata));
+ try pool.checkCancel();
+
+ _ = file;
+ _ = offset;
+ @panic("TODO");
+}
+
+fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekError!void {
+ const pool: *Pool = @ptrCast(@alignCast(userdata));
+ try pool.checkCancel();
+
+ _ = file;
+ _ = offset;
+ @panic("TODO");
}
fn pwrite(userdata: ?*anyopaque, file: Io.File, buffer: []const u8, offset: posix.off_t) Io.File.PWriteError!usize {
lib/std/Io/Writer.zig
@@ -5,7 +5,7 @@ const Writer = @This();
const std = @import("../std.zig");
const assert = std.debug.assert;
const Limit = std.Io.Limit;
-const File = std.fs.File;
+const File = std.Io.File;
const testing = std.testing;
const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;
lib/std/Io.zig
@@ -6,7 +6,6 @@ const windows = std.os.windows;
const posix = std.posix;
const math = std.math;
const assert = std.debug.assert;
-const fs = std.fs;
const Allocator = std.mem.Allocator;
const Alignment = std.mem.Alignment;
@@ -650,10 +649,15 @@ pub const VTable = struct {
conditionWake: *const fn (?*anyopaque, cond: *Condition, wake: Condition.Wake) void,
createFile: *const fn (?*anyopaque, dir: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File,
- openFile: *const fn (?*anyopaque, dir: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File,
- closeFile: *const fn (?*anyopaque, File) void,
- pread: *const fn (?*anyopaque, file: File, buffer: []u8, offset: std.posix.off_t) File.PReadError!usize,
+ fileOpen: *const fn (?*anyopaque, dir: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File,
+ fileClose: *const fn (?*anyopaque, File) void,
pwrite: *const fn (?*anyopaque, file: File, buffer: []const u8, offset: std.posix.off_t) File.PWriteError!usize,
+ /// Returns 0 on end of stream.
+ fileReadStreaming: *const fn (?*anyopaque, file: File, data: [][]u8) File.ReadStreamingError!usize,
+ /// Returns 0 on end of stream.
+ fileReadPositional: *const fn (?*anyopaque, file: File, data: [][]u8, offset: u64) File.ReadPositionalError!usize,
+ fileSeekBy: *const fn (?*anyopaque, file: File, offset: i64) File.SeekError!void,
+ fileSeekTo: *const fn (?*anyopaque, file: File, offset: u64) File.SeekError!void,
now: *const fn (?*anyopaque, clockid: std.posix.clockid_t) ClockGetTimeError!Timestamp,
sleep: *const fn (?*anyopaque, clockid: std.posix.clockid_t, deadline: Deadline) SleepError!void,
@@ -670,6 +674,18 @@ pub const Cancelable = error{
Canceled,
};
+pub const UnexpectedError = error{
+ /// The Operating System returned an undocumented error code.
+ ///
+ /// This error is in theory not possible, but it would be better
+ /// to handle this error than to invoke undefined behavior.
+ ///
+ /// When this error code is observed, it usually means the Zig Standard
+ /// Library needs a small patch to add the error code to the error set for
+ /// the respective function.
+ Unexpected,
+};
+
pub const Dir = struct {
handle: Handle,
@@ -680,7 +696,7 @@ pub const Dir = struct {
pub const Handle = std.posix.fd_t;
pub fn openFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
- return io.vtable.openFile(io.userdata, dir, sub_path, flags);
+ return io.vtable.fileOpen(io.userdata, dir, sub_path, flags);
}
pub fn createFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
@@ -706,66 +722,7 @@ pub const Dir = struct {
}
};
-pub const File = struct {
- handle: Handle,
-
- pub const Handle = std.posix.fd_t;
-
- pub const OpenFlags = fs.File.OpenFlags;
- pub const CreateFlags = fs.File.CreateFlags;
-
- pub const OpenError = fs.File.OpenError || Cancelable;
-
- pub fn close(file: File, io: Io) void {
- return io.vtable.closeFile(io.userdata, file);
- }
-
- pub const ReadError = fs.File.ReadError || Cancelable;
-
- pub fn read(file: File, io: Io, buffer: []u8) ReadError!usize {
- return @errorCast(file.pread(io, buffer, -1));
- }
-
- pub const PReadError = fs.File.PReadError || Cancelable;
-
- pub fn pread(file: File, io: Io, buffer: []u8, offset: std.posix.off_t) PReadError!usize {
- return io.vtable.pread(io.userdata, file, buffer, offset);
- }
-
- pub const WriteError = fs.File.WriteError || Cancelable;
-
- pub fn write(file: File, io: Io, buffer: []const u8) WriteError!usize {
- return @errorCast(file.pwrite(io, buffer, -1));
- }
-
- pub const PWriteError = fs.File.PWriteError || Cancelable;
-
- pub fn pwrite(file: File, io: Io, buffer: []const u8, offset: std.posix.off_t) PWriteError!usize {
- return io.vtable.pwrite(io.userdata, file, buffer, offset);
- }
-
- pub fn writeAll(file: File, io: Io, bytes: []const u8) WriteError!void {
- var index: usize = 0;
- while (index < bytes.len) {
- index += try file.write(io, bytes[index..]);
- }
- }
-
- pub fn readAll(file: File, io: Io, buffer: []u8) ReadError!usize {
- var index: usize = 0;
- while (index != buffer.len) {
- const amt = try file.read(io, buffer[index..]);
- if (amt == 0) break;
- index += amt;
- }
- return index;
- }
-
- pub fn openAbsolute(io: Io, absolute_path: []const u8, flags: OpenFlags) OpenError {
- assert(std.fs.path.isAbsolute(absolute_path));
- return Dir.cwd().openFile(io, absolute_path, flags);
- }
-};
+pub const File = @import("Io/File.zig");
pub const Timestamp = enum(i96) {
_,
lib/std/posix.zig
@@ -805,36 +805,7 @@ pub fn exit(status: u8) noreturn {
system.exit(status);
}
-pub const ReadError = error{
- InputOutput,
- SystemResources,
- IsDir,
- OperationAborted,
- BrokenPipe,
- ConnectionResetByPeer,
- ConnectionTimedOut,
- NotOpenForReading,
- SocketNotConnected,
-
- /// This error occurs when no global event loop is configured,
- /// and reading from the file descriptor would block.
- WouldBlock,
-
- /// reading a timerfd with CANCEL_ON_SET will lead to this error
- /// when the clock goes through a discontinuous change
- Canceled,
-
- /// In WASI, this error occurs when the file descriptor does
- /// not hold the required rights to read from it.
- AccessDenied,
-
- /// This error occurs in Linux if the process to be read from
- /// no longer exists.
- ProcessNotFound,
-
- /// Unable to read file due to lock.
- LockViolation,
-} || UnexpectedError;
+pub const ReadError = std.Io.File.ReadStreamingError;
/// Returns the number of bytes that were read, which can be less than
/// buf.len. If 0 bytes were read, that means EOF.
@@ -921,7 +892,6 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
/// a pointer within the address space of the application.
pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
if (native_os == .windows) {
- // TODO improve this to use ReadFileScatter
if (iov.len == 0) return 0;
const first = iov[0];
return read(fd, first.base[0..first.len]);
@@ -969,7 +939,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
}
}
-pub const PReadError = ReadError || error{Unseekable};
+pub const PReadError = std.Io.ReadPositionalError;
/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
///
@@ -5393,13 +5363,7 @@ pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void {
}
}
-pub const SeekError = error{
- Unseekable,
-
- /// In WASI, this error may occur when the file descriptor does
- /// not hold the required rights to seek on it.
- AccessDenied,
-} || UnexpectedError;
+pub const SeekError = std.Io.File.SeekError;
/// Repositions read/write file offset relative to the beginning.
pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void {
@@ -7572,7 +7536,7 @@ pub fn ioctl_SIOCGIFINDEX(fd: fd_t, ifr: *ifreq) IoCtl_SIOCGIFINDEX_Error!void {
}
}
-const lfs64_abi = native_os == .linux and builtin.link_libc and (builtin.abi.isGnu() or builtin.abi.isAndroid());
+pub const lfs64_abi = native_os == .linux and builtin.link_libc and (builtin.abi.isGnu() or builtin.abi.isAndroid());
/// Whether or not `error.Unexpected` will print its value and a stack trace.
///
@@ -7584,17 +7548,7 @@ pub const unexpected_error_tracing = builtin.mode == .Debug and switch (builtin.
else => false,
};
-pub const UnexpectedError = error{
- /// The Operating System returned an undocumented error code.
- ///
- /// This error is in theory not possible, but it would be better
- /// to handle this error than to invoke undefined behavior.
- ///
- /// When this error code is observed, it usually means the Zig Standard
- /// Library needs a small patch to add the error code to the error set for
- /// the respective function.
- Unexpected,
-};
+pub const UnexpectedError = std.Io.UnexpectedError;
/// Call this when you made a syscall or something that sets errno
/// and you get an unexpected error.