authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-09-05 17:30:07-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-02 16:30:59-07:00
log676f1b492ed8d311ff97335b4311302dfce9c0c8
treedc0b0e15307a5857f3bcee1018722f3417d5e350
parentbdf463bee2becd84b83bb9d66725420e03680df8

std: start moving fs.File to Io


8 files changed, 926 insertions(+), 314 deletions(-)

lib/std/Io.zig+22-65
...@@ -6,7 +6,6 @@ const windows = std.os.windows;...@@ -6,7 +6,6 @@ const windows = std.os.windows;
6const posix = std.posix;6const posix = std.posix;
7const math = std.math;7const math = std.math;
8const assert = std.debug.assert;8const assert = std.debug.assert;
9const fs = std.fs;
10const Allocator = std.mem.Allocator;9const Allocator = std.mem.Allocator;
11const Alignment = std.mem.Alignment;10const Alignment = std.mem.Alignment;
1211
...@@ -650,10 +649,15 @@ pub const VTable = struct {...@@ -650,10 +649,15 @@ pub const VTable = struct {
650 conditionWake: *const fn (?*anyopaque, cond: *Condition, wake: Condition.Wake) void,649 conditionWake: *const fn (?*anyopaque, cond: *Condition, wake: Condition.Wake) void,
651650
652 createFile: *const fn (?*anyopaque, dir: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File,651 createFile: *const fn (?*anyopaque, dir: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File,
653 openFile: *const fn (?*anyopaque, dir: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File,652 fileOpen: *const fn (?*anyopaque, dir: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File,
654 closeFile: *const fn (?*anyopaque, File) void,653 fileClose: *const fn (?*anyopaque, File) void,
655 pread: *const fn (?*anyopaque, file: File, buffer: []u8, offset: std.posix.off_t) File.PReadError!usize,
656 pwrite: *const fn (?*anyopaque, file: File, buffer: []const u8, offset: std.posix.off_t) File.PWriteError!usize,654 pwrite: *const fn (?*anyopaque, file: File, buffer: []const u8, offset: std.posix.off_t) File.PWriteError!usize,
655 /// Returns 0 on end of stream.
656 fileReadStreaming: *const fn (?*anyopaque, file: File, data: [][]u8) File.ReadStreamingError!usize,
657 /// Returns 0 on end of stream.
658 fileReadPositional: *const fn (?*anyopaque, file: File, data: [][]u8, offset: u64) File.ReadPositionalError!usize,
659 fileSeekBy: *const fn (?*anyopaque, file: File, offset: i64) File.SeekError!void,
660 fileSeekTo: *const fn (?*anyopaque, file: File, offset: u64) File.SeekError!void,
657661
658 now: *const fn (?*anyopaque, clockid: std.posix.clockid_t) ClockGetTimeError!Timestamp,662 now: *const fn (?*anyopaque, clockid: std.posix.clockid_t) ClockGetTimeError!Timestamp,
659 sleep: *const fn (?*anyopaque, clockid: std.posix.clockid_t, deadline: Deadline) SleepError!void,663 sleep: *const fn (?*anyopaque, clockid: std.posix.clockid_t, deadline: Deadline) SleepError!void,
...@@ -670,6 +674,18 @@ pub const Cancelable = error{...@@ -670,6 +674,18 @@ pub const Cancelable = error{
670 Canceled,674 Canceled,
671};675};
672676
677pub const UnexpectedError = error{
678 /// The Operating System returned an undocumented error code.
679 ///
680 /// This error is in theory not possible, but it would be better
681 /// to handle this error than to invoke undefined behavior.
682 ///
683 /// When this error code is observed, it usually means the Zig Standard
684 /// Library needs a small patch to add the error code to the error set for
685 /// the respective function.
686 Unexpected,
687};
688
673pub const Dir = struct {689pub const Dir = struct {
674 handle: Handle,690 handle: Handle,
675691
...@@ -680,7 +696,7 @@ pub const Dir = struct {...@@ -680,7 +696,7 @@ pub const Dir = struct {
680 pub const Handle = std.posix.fd_t;696 pub const Handle = std.posix.fd_t;
681697
682 pub fn openFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {698 pub fn openFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
683 return io.vtable.openFile(io.userdata, dir, sub_path, flags);699 return io.vtable.fileOpen(io.userdata, dir, sub_path, flags);
684 }700 }
685701
686 pub fn createFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {702 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 {...@@ -706,66 +722,7 @@ pub const Dir = struct {
706 }722 }
707};723};
708724
709pub const File = struct {725pub const File = @import("Io/File.zig");
710 handle: Handle,
711
712 pub const Handle = std.posix.fd_t;
713
714 pub const OpenFlags = fs.File.OpenFlags;
715 pub const CreateFlags = fs.File.CreateFlags;
716
717 pub const OpenError = fs.File.OpenError || Cancelable;
718
719 pub fn close(file: File, io: Io) void {
720 return io.vtable.closeFile(io.userdata, file);
721 }
722
723 pub const ReadError = fs.File.ReadError || Cancelable;
724
725 pub fn read(file: File, io: Io, buffer: []u8) ReadError!usize {
726 return @errorCast(file.pread(io, buffer, -1));
727 }
728
729 pub const PReadError = fs.File.PReadError || Cancelable;
730
731 pub fn pread(file: File, io: Io, buffer: []u8, offset: std.posix.off_t) PReadError!usize {
732 return io.vtable.pread(io.userdata, file, buffer, offset);
733 }
734
735 pub const WriteError = fs.File.WriteError || Cancelable;
736
737 pub fn write(file: File, io: Io, buffer: []const u8) WriteError!usize {
738 return @errorCast(file.pwrite(io, buffer, -1));
739 }
740
741 pub const PWriteError = fs.File.PWriteError || Cancelable;
742
743 pub fn pwrite(file: File, io: Io, buffer: []const u8, offset: std.posix.off_t) PWriteError!usize {
744 return io.vtable.pwrite(io.userdata, file, buffer, offset);
745 }
746
747 pub fn writeAll(file: File, io: Io, bytes: []const u8) WriteError!void {
748 var index: usize = 0;
749 while (index < bytes.len) {
750 index += try file.write(io, bytes[index..]);
751 }
752 }
753
754 pub fn readAll(file: File, io: Io, buffer: []u8) ReadError!usize {
755 var index: usize = 0;
756 while (index != buffer.len) {
757 const amt = try file.read(io, buffer[index..]);
758 if (amt == 0) break;
759 index += amt;
760 }
761 return index;
762 }
763
764 pub fn openAbsolute(io: Io, absolute_path: []const u8, flags: OpenFlags) OpenError {
765 assert(std.fs.path.isAbsolute(absolute_path));
766 return Dir.cwd().openFile(io, absolute_path, flags);
767 }
768};
769726
770pub const Timestamp = enum(i96) {727pub const Timestamp = enum(i96) {
771 _,728 _,
lib/std/Io/EventLoop.zig+28-28
...@@ -93,7 +93,7 @@ const Fiber = struct {...@@ -93,7 +93,7 @@ const Fiber = struct {
93 }93 }
9494
95 fn resultPointer(f: *Fiber, comptime Result: type) *Result {95 fn resultPointer(f: *Fiber, comptime Result: type) *Result {
96 return @alignCast(@ptrCast(f.resultBytes(.of(Result))));96 return @ptrCast(@alignCast(f.resultBytes(.of(Result))));
97 }97 }
9898
99 fn resultBytes(f: *Fiber, alignment: Alignment) [*]u8 {99 fn resultBytes(f: *Fiber, alignment: Alignment) [*]u8 {
...@@ -153,8 +153,8 @@ pub fn io(el: *EventLoop) Io {...@@ -153,8 +153,8 @@ pub fn io(el: *EventLoop) Io {
153 .conditionWake = conditionWake,153 .conditionWake = conditionWake,
154154
155 .createFile = createFile,155 .createFile = createFile,
156 .openFile = openFile,156 .fileOpen = fileOpen,
157 .closeFile = closeFile,157 .fileClose = fileClose,
158 .pread = pread,158 .pread = pread,
159 .pwrite = pwrite,159 .pwrite = pwrite,
160160
...@@ -193,7 +193,7 @@ pub fn init(el: *EventLoop, gpa: Allocator) !void {...@@ -193,7 +193,7 @@ pub fn init(el: *EventLoop, gpa: Allocator) !void {
193 };193 };
194 const main_thread = &el.threads.allocated[0];194 const main_thread = &el.threads.allocated[0];
195 Thread.self = main_thread;195 Thread.self = main_thread;
196 const idle_stack_end: [*]align(16) usize = @alignCast(@ptrCast(allocated_slice[idle_stack_end_offset..].ptr));196 const idle_stack_end: [*]align(16) usize = @ptrCast(@alignCast(allocated_slice[idle_stack_end_offset..].ptr));
197 (idle_stack_end - 1)[0..1].* = .{@intFromPtr(el)};197 (idle_stack_end - 1)[0..1].* = .{@intFromPtr(el)};
198 main_thread.* = .{198 main_thread.* = .{
199 .thread = undefined,199 .thread = undefined,
...@@ -244,7 +244,7 @@ pub fn deinit(el: *EventLoop) void {...@@ -244,7 +244,7 @@ pub fn deinit(el: *EventLoop) void {
244 assert(ready_fiber == null or ready_fiber == Fiber.finished); // pending async244 assert(ready_fiber == null or ready_fiber == Fiber.finished); // pending async
245 }245 }
246 el.yield(null, .exit);246 el.yield(null, .exit);
247 const allocated_ptr: [*]align(@alignOf(Thread)) u8 = @alignCast(@ptrCast(el.threads.allocated.ptr));247 const allocated_ptr: [*]align(@alignOf(Thread)) u8 = @ptrCast(@alignCast(el.threads.allocated.ptr));
248 const idle_stack_end_offset = std.mem.alignForward(usize, el.threads.allocated.len * @sizeOf(Thread) + idle_stack_size, std.heap.page_size_max);248 const idle_stack_end_offset = std.mem.alignForward(usize, el.threads.allocated.len * @sizeOf(Thread) + idle_stack_size, std.heap.page_size_max);
249 for (el.threads.allocated[1..active_threads]) |*thread| thread.thread.join();249 for (el.threads.allocated[1..active_threads]) |*thread| thread.thread.join();
250 el.gpa.free(allocated_ptr[0..idle_stack_end_offset]);250 el.gpa.free(allocated_ptr[0..idle_stack_end_offset]);
...@@ -530,7 +530,7 @@ const SwitchMessage = struct {...@@ -530,7 +530,7 @@ const SwitchMessage = struct {
530 const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev));530 const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev));
531 assert(prev_fiber.queue_next == null);531 assert(prev_fiber.queue_next == null);
532 for (futures) |any_future| {532 for (futures) |any_future| {
533 const future_fiber: *Fiber = @alignCast(@ptrCast(any_future));533 const future_fiber: *Fiber = @ptrCast(@alignCast(any_future));
534 if (@atomicRmw(?*Fiber, &future_fiber.awaiter, .Xchg, prev_fiber, .acq_rel) == Fiber.finished) {534 if (@atomicRmw(?*Fiber, &future_fiber.awaiter, .Xchg, prev_fiber, .acq_rel) == Fiber.finished) {
535 const closure: *AsyncClosure = .fromFiber(future_fiber);535 const closure: *AsyncClosure = .fromFiber(future_fiber);
536 if (!@atomicRmw(bool, &closure.already_awaited, .Xchg, true, .seq_cst)) {536 if (!@atomicRmw(bool, &closure.already_awaited, .Xchg, true, .seq_cst)) {
...@@ -897,12 +897,12 @@ fn asyncConcurrent(...@@ -897,12 +897,12 @@ fn asyncConcurrent(
897 assert(result_len <= Fiber.max_result_size); // TODO897 assert(result_len <= Fiber.max_result_size); // TODO
898 assert(context.len <= Fiber.max_context_size); // TODO898 assert(context.len <= Fiber.max_context_size); // TODO
899899
900 const event_loop: *EventLoop = @alignCast(@ptrCast(userdata));900 const event_loop: *EventLoop = @ptrCast(@alignCast(userdata));
901 const fiber = try Fiber.allocate(event_loop);901 const fiber = try Fiber.allocate(event_loop);
902 std.log.debug("allocated {*}", .{fiber});902 std.log.debug("allocated {*}", .{fiber});
903903
904 const closure: *AsyncClosure = .fromFiber(fiber);904 const closure: *AsyncClosure = .fromFiber(fiber);
905 const stack_end: [*]align(16) usize = @alignCast(@ptrCast(closure));905 const stack_end: [*]align(16) usize = @ptrCast(@alignCast(closure));
906 (stack_end - 1)[0..1].* = .{@intFromPtr(&AsyncClosure.call)};906 (stack_end - 1)[0..1].* = .{@intFromPtr(&AsyncClosure.call)};
907 fiber.* = .{907 fiber.* = .{
908 .required_align = {},908 .required_align = {},
...@@ -974,7 +974,7 @@ fn asyncDetached(...@@ -974,7 +974,7 @@ fn asyncDetached(
974 assert(context_alignment.compare(.lte, Fiber.max_context_align)); // TODO974 assert(context_alignment.compare(.lte, Fiber.max_context_align)); // TODO
975 assert(context.len <= Fiber.max_context_size); // TODO975 assert(context.len <= Fiber.max_context_size); // TODO
976976
977 const event_loop: *EventLoop = @alignCast(@ptrCast(userdata));977 const event_loop: *EventLoop = @ptrCast(@alignCast(userdata));
978 const fiber = Fiber.allocate(event_loop) catch {978 const fiber = Fiber.allocate(event_loop) catch {
979 start(context.ptr);979 start(context.ptr);
980 return;980 return;
...@@ -985,7 +985,7 @@ fn asyncDetached(...@@ -985,7 +985,7 @@ fn asyncDetached(
985 const closure: *DetachedClosure = @ptrFromInt(Fiber.max_context_align.max(.of(DetachedClosure)).backward(985 const closure: *DetachedClosure = @ptrFromInt(Fiber.max_context_align.max(.of(DetachedClosure)).backward(
986 @intFromPtr(fiber.allocatedEnd()) - Fiber.max_context_size,986 @intFromPtr(fiber.allocatedEnd()) - Fiber.max_context_size,
987 ) - @sizeOf(DetachedClosure));987 ) - @sizeOf(DetachedClosure));
988 const stack_end: [*]align(16) usize = @alignCast(@ptrCast(closure));988 const stack_end: [*]align(16) usize = @ptrCast(@alignCast(closure));
989 (stack_end - 1)[0..1].* = .{@intFromPtr(&DetachedClosure.call)};989 (stack_end - 1)[0..1].* = .{@intFromPtr(&DetachedClosure.call)};
990 fiber.* = .{990 fiber.* = .{
991 .required_align = {},991 .required_align = {},
...@@ -1035,8 +1035,8 @@ fn await(...@@ -1035,8 +1035,8 @@ fn await(
1035 result: []u8,1035 result: []u8,
1036 result_alignment: Alignment,1036 result_alignment: Alignment,
1037) void {1037) void {
1038 const event_loop: *EventLoop = @alignCast(@ptrCast(userdata));1038 const event_loop: *EventLoop = @ptrCast(@alignCast(userdata));
1039 const future_fiber: *Fiber = @alignCast(@ptrCast(any_future));1039 const future_fiber: *Fiber = @ptrCast(@alignCast(any_future));
1040 if (@atomicLoad(?*Fiber, &future_fiber.awaiter, .acquire) != Fiber.finished)1040 if (@atomicLoad(?*Fiber, &future_fiber.awaiter, .acquire) != Fiber.finished)
1041 event_loop.yield(null, .{ .register_awaiter = &future_fiber.awaiter });1041 event_loop.yield(null, .{ .register_awaiter = &future_fiber.awaiter });
1042 @memcpy(result, future_fiber.resultBytes(result_alignment));1042 @memcpy(result, future_fiber.resultBytes(result_alignment));
...@@ -1044,11 +1044,11 @@ fn await(...@@ -1044,11 +1044,11 @@ fn await(
1044}1044}
10451045
1046fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize {1046fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize {
1047 const el: *EventLoop = @alignCast(@ptrCast(userdata));1047 const el: *EventLoop = @ptrCast(@alignCast(userdata));
10481048
1049 // Optimization to avoid the yield below.1049 // Optimization to avoid the yield below.
1050 for (futures, 0..) |any_future, i| {1050 for (futures, 0..) |any_future, i| {
1051 const future_fiber: *Fiber = @alignCast(@ptrCast(any_future));1051 const future_fiber: *Fiber = @ptrCast(@alignCast(any_future));
1052 if (@atomicLoad(?*Fiber, &future_fiber.awaiter, .acquire) == Fiber.finished)1052 if (@atomicLoad(?*Fiber, &future_fiber.awaiter, .acquire) == Fiber.finished)
1053 return i;1053 return i;
1054 }1054 }
...@@ -1062,7 +1062,7 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize {...@@ -1062,7 +1062,7 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize {
1062 var result: ?usize = null;1062 var result: ?usize = null;
10631063
1064 for (futures, 0..) |any_future, i| {1064 for (futures, 0..) |any_future, i| {
1065 const future_fiber: *Fiber = @alignCast(@ptrCast(any_future));1065 const future_fiber: *Fiber = @ptrCast(@alignCast(any_future));
1066 if (@cmpxchgStrong(?*Fiber, &future_fiber.awaiter, my_fiber, null, .seq_cst, .seq_cst)) |awaiter| {1066 if (@cmpxchgStrong(?*Fiber, &future_fiber.awaiter, my_fiber, null, .seq_cst, .seq_cst)) |awaiter| {
1067 if (awaiter == Fiber.finished) {1067 if (awaiter == Fiber.finished) {
1068 if (result == null) result = i;1068 if (result == null) result = i;
...@@ -1085,7 +1085,7 @@ fn cancel(...@@ -1085,7 +1085,7 @@ fn cancel(
1085 result: []u8,1085 result: []u8,
1086 result_alignment: Alignment,1086 result_alignment: Alignment,
1087) void {1087) void {
1088 const future_fiber: *Fiber = @alignCast(@ptrCast(any_future));1088 const future_fiber: *Fiber = @ptrCast(@alignCast(any_future));
1089 if (@atomicRmw(1089 if (@atomicRmw(
1090 ?*Thread,1090 ?*Thread,
1091 &future_fiber.cancel_thread,1091 &future_fiber.cancel_thread,
...@@ -1124,7 +1124,7 @@ fn createFile(...@@ -1124,7 +1124,7 @@ fn createFile(
1124 sub_path: []const u8,1124 sub_path: []const u8,
1125 flags: Io.File.CreateFlags,1125 flags: Io.File.CreateFlags,
1126) Io.File.OpenError!Io.File {1126) Io.File.OpenError!Io.File {
1127 const el: *EventLoop = @alignCast(@ptrCast(userdata));1127 const el: *EventLoop = @ptrCast(@alignCast(userdata));
1128 const thread: *Thread = .current();1128 const thread: *Thread = .current();
1129 const iou = &thread.io_uring;1129 const iou = &thread.io_uring;
1130 const fiber = thread.currentFiber();1130 const fiber = thread.currentFiber();
...@@ -1220,13 +1220,13 @@ fn createFile(...@@ -1220,13 +1220,13 @@ fn createFile(
1220 }1220 }
1221}1221}
12221222
1223fn openFile(1223fn fileOpen(
1224 userdata: ?*anyopaque,1224 userdata: ?*anyopaque,
1225 dir: Io.Dir,1225 dir: Io.Dir,
1226 sub_path: []const u8,1226 sub_path: []const u8,
1227 flags: Io.File.OpenFlags,1227 flags: Io.File.OpenFlags,
1228) Io.File.OpenError!Io.File {1228) Io.File.OpenError!Io.File {
1229 const el: *EventLoop = @alignCast(@ptrCast(userdata));1229 const el: *EventLoop = @ptrCast(@alignCast(userdata));
1230 const thread: *Thread = .current();1230 const thread: *Thread = .current();
1231 const iou = &thread.io_uring;1231 const iou = &thread.io_uring;
1232 const fiber = thread.currentFiber();1232 const fiber = thread.currentFiber();
...@@ -1328,8 +1328,8 @@ fn openFile(...@@ -1328,8 +1328,8 @@ fn openFile(
1328 }1328 }
1329}1329}
13301330
1331fn closeFile(userdata: ?*anyopaque, file: Io.File) void {1331fn fileClose(userdata: ?*anyopaque, file: Io.File) void {
1332 const el: *EventLoop = @alignCast(@ptrCast(userdata));1332 const el: *EventLoop = @ptrCast(@alignCast(userdata));
1333 const thread: *Thread = .current();1333 const thread: *Thread = .current();
1334 const iou = &thread.io_uring;1334 const iou = &thread.io_uring;
1335 const fiber = thread.currentFiber();1335 const fiber = thread.currentFiber();
...@@ -1365,7 +1365,7 @@ fn closeFile(userdata: ?*anyopaque, file: Io.File) void {...@@ -1365,7 +1365,7 @@ fn closeFile(userdata: ?*anyopaque, file: Io.File) void {
1365}1365}
13661366
1367fn pread(userdata: ?*anyopaque, file: Io.File, buffer: []u8, offset: std.posix.off_t) Io.File.PReadError!usize {1367fn pread(userdata: ?*anyopaque, file: Io.File, buffer: []u8, offset: std.posix.off_t) Io.File.PReadError!usize {
1368 const el: *EventLoop = @alignCast(@ptrCast(userdata));1368 const el: *EventLoop = @ptrCast(@alignCast(userdata));
1369 const thread: *Thread = .current();1369 const thread: *Thread = .current();
1370 const iou = &thread.io_uring;1370 const iou = &thread.io_uring;
1371 const fiber = thread.currentFiber();1371 const fiber = thread.currentFiber();
...@@ -1417,7 +1417,7 @@ fn pread(userdata: ?*anyopaque, file: Io.File, buffer: []u8, offset: std.posix.o...@@ -1417,7 +1417,7 @@ fn pread(userdata: ?*anyopaque, file: Io.File, buffer: []u8, offset: std.posix.o
1417}1417}
14181418
1419fn pwrite(userdata: ?*anyopaque, file: Io.File, buffer: []const u8, offset: std.posix.off_t) Io.File.PWriteError!usize {1419fn pwrite(userdata: ?*anyopaque, file: Io.File, buffer: []const u8, offset: std.posix.off_t) Io.File.PWriteError!usize {
1420 const el: *EventLoop = @alignCast(@ptrCast(userdata));1420 const el: *EventLoop = @ptrCast(@alignCast(userdata));
1421 const thread: *Thread = .current();1421 const thread: *Thread = .current();
1422 const iou = &thread.io_uring;1422 const iou = &thread.io_uring;
1423 const fiber = thread.currentFiber();1423 const fiber = thread.currentFiber();
...@@ -1479,7 +1479,7 @@ fn now(userdata: ?*anyopaque, clockid: std.posix.clockid_t) Io.ClockGetTimeError...@@ -1479,7 +1479,7 @@ fn now(userdata: ?*anyopaque, clockid: std.posix.clockid_t) Io.ClockGetTimeError
1479}1479}
14801480
1481fn sleep(userdata: ?*anyopaque, clockid: std.posix.clockid_t, deadline: Io.Deadline) Io.SleepError!void {1481fn sleep(userdata: ?*anyopaque, clockid: std.posix.clockid_t, deadline: Io.Deadline) Io.SleepError!void {
1482 const el: *EventLoop = @alignCast(@ptrCast(userdata));1482 const el: *EventLoop = @ptrCast(@alignCast(userdata));
1483 const thread: *Thread = .current();1483 const thread: *Thread = .current();
1484 const iou = &thread.io_uring;1484 const iou = &thread.io_uring;
1485 const fiber = thread.currentFiber();1485 const fiber = thread.currentFiber();
...@@ -1532,7 +1532,7 @@ fn sleep(userdata: ?*anyopaque, clockid: std.posix.clockid_t, deadline: Io.Deadl...@@ -1532,7 +1532,7 @@ fn sleep(userdata: ?*anyopaque, clockid: std.posix.clockid_t, deadline: Io.Deadl
1532}1532}
15331533
1534fn mutexLock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) error{Canceled}!void {1534fn mutexLock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) error{Canceled}!void {
1535 const el: *EventLoop = @alignCast(@ptrCast(userdata));1535 const el: *EventLoop = @ptrCast(@alignCast(userdata));
1536 el.yield(null, .{ .mutex_lock = .{ .prev_state = prev_state, .mutex = mutex } });1536 el.yield(null, .{ .mutex_lock = .{ .prev_state = prev_state, .mutex = mutex } });
1537}1537}
1538fn mutexUnlock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) void {1538fn 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...@@ -1553,7 +1553,7 @@ fn mutexUnlock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mut
1553 .acquire,1553 .acquire,
1554 ) orelse return) |next_state| maybe_waiting_fiber = @ptrFromInt(@intFromEnum(next_state));1554 ) orelse return) |next_state| maybe_waiting_fiber = @ptrFromInt(@intFromEnum(next_state));
1555 maybe_waiting_fiber.?.queue_next = null;1555 maybe_waiting_fiber.?.queue_next = null;
1556 const el: *EventLoop = @alignCast(@ptrCast(userdata));1556 const el: *EventLoop = @ptrCast(@alignCast(userdata));
1557 el.yield(maybe_waiting_fiber.?, .reschedule);1557 el.yield(maybe_waiting_fiber.?, .reschedule);
1558}1558}
15591559
...@@ -1566,7 +1566,7 @@ const ConditionImpl = struct {...@@ -1566,7 +1566,7 @@ const ConditionImpl = struct {
1566};1566};
15671567
1568fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) Io.Cancelable!void {1568fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) Io.Cancelable!void {
1569 const el: *EventLoop = @alignCast(@ptrCast(userdata));1569 const el: *EventLoop = @ptrCast(@alignCast(userdata));
1570 el.yield(null, .{ .condition_wait = .{ .cond = cond, .mutex = mutex } });1570 el.yield(null, .{ .condition_wait = .{ .cond = cond, .mutex = mutex } });
1571 const thread = Thread.current();1571 const thread = Thread.current();
1572 const fiber = thread.currentFiber();1572 const fiber = thread.currentFiber();
...@@ -1595,7 +1595,7 @@ fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) I...@@ -1595,7 +1595,7 @@ fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) I
1595}1595}
15961596
1597fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.Wake) void {1597fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.Wake) void {
1598 const el: *EventLoop = @alignCast(@ptrCast(userdata));1598 const el: *EventLoop = @ptrCast(@alignCast(userdata));
1599 const waiting_fiber = @atomicRmw(?*Fiber, @as(*?*Fiber, @ptrCast(&cond.state)), .Xchg, null, .acquire) orelse return;1599 const waiting_fiber = @atomicRmw(?*Fiber, @as(*?*Fiber, @ptrCast(&cond.state)), .Xchg, null, .acquire) orelse return;
1600 waiting_fiber.resultPointer(ConditionImpl).event = .{ .wake = wake };1600 waiting_fiber.resultPointer(ConditionImpl).event = .{ .wake = wake };
1601 el.yield(waiting_fiber, .reschedule);1601 el.yield(waiting_fiber, .reschedule);
lib/std/Io/File.zig created+550
...@@ -0,0 +1,550 @@
1const builtin = @import("builtin");
2const std = @import("../std.zig");
3const Io = std.Io;
4const File = @This();
5const assert = std.debug.assert;
6
7handle: Handle,
8
9pub const Handle = std.posix.fd_t;
10pub const Mode = std.posix.mode_t;
11pub const INode = std.posix.ino_t;
12
13pub const Kind = enum {
14 block_device,
15 character_device,
16 directory,
17 named_pipe,
18 sym_link,
19 file,
20 unix_domain_socket,
21 whiteout,
22 door,
23 event_port,
24 unknown,
25};
26
27pub const Stat = struct {
28 /// A number that the system uses to point to the file metadata. This
29 /// number is not guaranteed to be unique across time, as some file
30 /// systems may reuse an inode after its file has been deleted. Some
31 /// systems may change the inode of a file over time.
32 ///
33 /// On Linux, the inode is a structure that stores the metadata, and
34 /// the inode _number_ is what you see here: the index number of the
35 /// inode.
36 ///
37 /// The FileIndex on Windows is similar. It is a number for a file that
38 /// is unique to each filesystem.
39 inode: INode,
40 size: u64,
41 /// This is available on POSIX systems and is always 0 otherwise.
42 mode: Mode,
43 kind: Kind,
44
45 /// Last access time in nanoseconds, relative to UTC 1970-01-01.
46 atime: i128,
47 /// Last modification time in nanoseconds, relative to UTC 1970-01-01.
48 mtime: i128,
49 /// Last status/metadata change time in nanoseconds, relative to UTC 1970-01-01.
50 ctime: i128,
51
52 pub fn fromPosix(st: std.posix.Stat) Stat {
53 const atime = st.atime();
54 const mtime = st.mtime();
55 const ctime = st.ctime();
56 return .{
57 .inode = st.ino,
58 .size = @bitCast(st.size),
59 .mode = st.mode,
60 .kind = k: {
61 const m = st.mode & std.posix.S.IFMT;
62 switch (m) {
63 std.posix.S.IFBLK => break :k .block_device,
64 std.posix.S.IFCHR => break :k .character_device,
65 std.posix.S.IFDIR => break :k .directory,
66 std.posix.S.IFIFO => break :k .named_pipe,
67 std.posix.S.IFLNK => break :k .sym_link,
68 std.posix.S.IFREG => break :k .file,
69 std.posix.S.IFSOCK => break :k .unix_domain_socket,
70 else => {},
71 }
72 if (builtin.os.tag.isSolarish()) switch (m) {
73 std.posix.S.IFDOOR => break :k .door,
74 std.posix.S.IFPORT => break :k .event_port,
75 else => {},
76 };
77
78 break :k .unknown;
79 },
80 .atime = @as(i128, atime.sec) * std.time.ns_per_s + atime.nsec,
81 .mtime = @as(i128, mtime.sec) * std.time.ns_per_s + mtime.nsec,
82 .ctime = @as(i128, ctime.sec) * std.time.ns_per_s + ctime.nsec,
83 };
84 }
85
86 pub fn fromLinux(stx: std.os.linux.Statx) Stat {
87 const atime = stx.atime;
88 const mtime = stx.mtime;
89 const ctime = stx.ctime;
90
91 return .{
92 .inode = stx.ino,
93 .size = stx.size,
94 .mode = stx.mode,
95 .kind = switch (stx.mode & std.os.linux.S.IFMT) {
96 std.os.linux.S.IFDIR => .directory,
97 std.os.linux.S.IFCHR => .character_device,
98 std.os.linux.S.IFBLK => .block_device,
99 std.os.linux.S.IFREG => .file,
100 std.os.linux.S.IFIFO => .named_pipe,
101 std.os.linux.S.IFLNK => .sym_link,
102 std.os.linux.S.IFSOCK => .unix_domain_socket,
103 else => .unknown,
104 },
105 .atime = @as(i128, atime.sec) * std.time.ns_per_s + atime.nsec,
106 .mtime = @as(i128, mtime.sec) * std.time.ns_per_s + mtime.nsec,
107 .ctime = @as(i128, ctime.sec) * std.time.ns_per_s + ctime.nsec,
108 };
109 }
110
111 pub fn fromWasi(st: std.os.wasi.filestat_t) Stat {
112 return .{
113 .inode = st.ino,
114 .size = @bitCast(st.size),
115 .mode = 0,
116 .kind = switch (st.filetype) {
117 .BLOCK_DEVICE => .block_device,
118 .CHARACTER_DEVICE => .character_device,
119 .DIRECTORY => .directory,
120 .SYMBOLIC_LINK => .sym_link,
121 .REGULAR_FILE => .file,
122 .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
123 else => .unknown,
124 },
125 .atime = st.atim,
126 .mtime = st.mtim,
127 .ctime = st.ctim,
128 };
129 }
130};
131
132pub const StatError = std.posix.FStatError || Io.Cancelable;
133
134/// Returns `Stat` containing basic information about the `File`.
135pub fn stat(file: File, io: Io) StatError!Stat {
136 _ = file;
137 _ = io;
138 @panic("TODO");
139}
140
141pub const OpenFlags = std.fs.File.OpenFlags;
142pub const CreateFlags = std.fs.File.CreateFlags;
143
144pub const OpenError = std.fs.File.OpenError || Io.Cancelable;
145
146pub fn close(file: File, io: Io) void {
147 return io.vtable.fileClose(io.userdata, file);
148}
149
150pub const ReadStreamingError = error{
151 InputOutput,
152 SystemResources,
153 IsDir,
154 BrokenPipe,
155 ConnectionResetByPeer,
156 ConnectionTimedOut,
157 NotOpenForReading,
158 SocketNotConnected,
159 /// This error occurs when no global event loop is configured,
160 /// and reading from the file descriptor would block.
161 WouldBlock,
162 /// In WASI, this error occurs when the file descriptor does
163 /// not hold the required rights to read from it.
164 AccessDenied,
165 /// This error occurs in Linux if the process to be read from
166 /// no longer exists.
167 ProcessNotFound,
168 /// Unable to read file due to lock.
169 LockViolation,
170} || Io.Cancelable || Io.UnexpectedError;
171
172pub const ReadPositionalError = ReadStreamingError || error{Unseekable};
173
174pub fn readPositional(file: File, io: Io, buffer: []u8, offset: u64) ReadPositionalError!usize {
175 return io.vtable.pread(io.userdata, file, buffer, offset);
176}
177
178pub const WriteError = std.fs.File.WriteError || Io.Cancelable;
179
180pub fn write(file: File, io: Io, buffer: []const u8) WriteError!usize {
181 return @errorCast(file.pwrite(io, buffer, -1));
182}
183
184pub const PWriteError = std.fs.File.PWriteError || Io.Cancelable;
185
186pub fn pwrite(file: File, io: Io, buffer: []const u8, offset: std.posix.off_t) PWriteError!usize {
187 return io.vtable.pwrite(io.userdata, file, buffer, offset);
188}
189
190pub fn openAbsolute(io: Io, absolute_path: []const u8, flags: OpenFlags) OpenError!File {
191 assert(std.fs.path.isAbsolute(absolute_path));
192 return Io.Dir.cwd().openFile(io, absolute_path, flags);
193}
194
195/// Defaults to positional reading; falls back to streaming.
196///
197/// Positional is more threadsafe, since the global seek position is not
198/// affected.
199pub fn reader(file: File, io: Io, buffer: []u8) Reader {
200 return .init(file, io, buffer);
201}
202
203/// Positional is more threadsafe, since the global seek position is not
204/// affected, but when such syscalls are not available, preemptively
205/// initializing in streaming mode skips a failed syscall.
206pub fn readerStreaming(file: File, io: Io, buffer: []u8) Reader {
207 return .initStreaming(file, io, buffer);
208}
209
210pub const SeekError = error{
211 Unseekable,
212 /// The file descriptor does not hold the required rights to seek on it.
213 AccessDenied,
214} || Io.Cancelable || Io.UnexpectedError;
215
216/// Memoizes key information about a file handle such as:
217/// * The size from calling stat, or the error that occurred therein.
218/// * The current seek position.
219/// * The error that occurred when trying to seek.
220/// * Whether reading should be done positionally or streaming.
221/// * Whether reading should be done via fd-to-fd syscalls (e.g. `sendfile`)
222/// versus plain variants (e.g. `read`).
223///
224/// Fulfills the `Io.Reader` interface.
225pub const Reader = struct {
226 io: Io,
227 file: File,
228 err: ?Error = null,
229 mode: Reader.Mode = .positional,
230 /// Tracks the true seek position in the file. To obtain the logical
231 /// position, use `logicalPos`.
232 pos: u64 = 0,
233 size: ?u64 = null,
234 size_err: ?SizeError = null,
235 seek_err: ?Reader.SeekError = null,
236 interface: Io.Reader,
237
238 pub const Error = std.posix.ReadError || Io.Cancelable;
239
240 pub const SizeError = std.os.windows.GetFileSizeError || StatError || error{
241 /// Occurs if, for example, the file handle is a network socket and therefore does not have a size.
242 Streaming,
243 };
244
245 pub const SeekError = File.SeekError || error{
246 /// Seeking fell back to reading, and reached the end before the requested seek position.
247 /// `pos` remains at the end of the file.
248 EndOfStream,
249 /// Seeking fell back to reading, which failed.
250 ReadFailed,
251 };
252
253 pub const Mode = enum {
254 streaming,
255 positional,
256 /// Avoid syscalls other than `read` and `readv`.
257 streaming_reading,
258 /// Avoid syscalls other than `pread` and `preadv`.
259 positional_reading,
260 /// Indicates reading cannot continue because of a seek failure.
261 failure,
262
263 pub fn toStreaming(m: @This()) @This() {
264 return switch (m) {
265 .positional, .streaming => .streaming,
266 .positional_reading, .streaming_reading => .streaming_reading,
267 .failure => .failure,
268 };
269 }
270
271 pub fn toReading(m: @This()) @This() {
272 return switch (m) {
273 .positional, .positional_reading => .positional_reading,
274 .streaming, .streaming_reading => .streaming_reading,
275 .failure => .failure,
276 };
277 }
278 };
279
280 pub fn initInterface(buffer: []u8) Io.Reader {
281 return .{
282 .vtable = &.{
283 .stream = Reader.stream,
284 .discard = Reader.discard,
285 .readVec = Reader.readVec,
286 },
287 .buffer = buffer,
288 .seek = 0,
289 .end = 0,
290 };
291 }
292
293 pub fn init(file: File, io: Io, buffer: []u8) Reader {
294 return .{
295 .io = io,
296 .file = file,
297 .interface = initInterface(buffer),
298 };
299 }
300
301 pub fn initSize(file: File, io: Io, buffer: []u8, size: ?u64) Reader {
302 return .{
303 .io = io,
304 .file = file,
305 .interface = initInterface(buffer),
306 .size = size,
307 };
308 }
309
310 /// Positional is more threadsafe, since the global seek position is not
311 /// affected, but when such syscalls are not available, preemptively
312 /// initializing in streaming mode skips a failed syscall.
313 pub fn initStreaming(file: File, io: Io, buffer: []u8) Reader {
314 return .{
315 .io = io,
316 .file = file,
317 .interface = Reader.initInterface(buffer),
318 .mode = .streaming,
319 .seek_err = error.Unseekable,
320 .size_err = error.Streaming,
321 };
322 }
323
324 pub fn getSize(r: *Reader) SizeError!u64 {
325 return r.size orelse {
326 if (r.size_err) |err| return err;
327 if (std.posix.Stat == void) {
328 r.size_err = error.Streaming;
329 return error.Streaming;
330 }
331 if (stat(r.file, r.io)) |st| {
332 if (st.kind == .file) {
333 r.size = st.size;
334 return st.size;
335 } else {
336 r.mode = r.mode.toStreaming();
337 r.size_err = error.Streaming;
338 return error.Streaming;
339 }
340 } else |err| {
341 r.size_err = err;
342 return err;
343 }
344 };
345 }
346
347 pub fn seekBy(r: *Reader, offset: i64) Reader.SeekError!void {
348 const io = r.io;
349 switch (r.mode) {
350 .positional, .positional_reading => {
351 setPosAdjustingBuffer(r, @intCast(@as(i64, @intCast(r.pos)) + offset));
352 },
353 .streaming, .streaming_reading => {
354 if (std.posix.SEEK == void) {
355 r.seek_err = error.Unseekable;
356 return error.Unseekable;
357 }
358 const seek_err = r.seek_err orelse e: {
359 if (io.vtable.fileSeekBy(io.userdata, r.file, offset)) |_| {
360 setPosAdjustingBuffer(r, @intCast(@as(i64, @intCast(r.pos)) + offset));
361 return;
362 } else |err| {
363 r.seek_err = err;
364 break :e err;
365 }
366 };
367 var remaining = std.math.cast(u64, offset) orelse return seek_err;
368 while (remaining > 0) {
369 remaining -= discard(&r.interface, .limited64(remaining)) catch |err| {
370 r.seek_err = err;
371 return err;
372 };
373 }
374 r.interface.seek = 0;
375 r.interface.end = 0;
376 },
377 .failure => return r.seek_err.?,
378 }
379 }
380
381 pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void {
382 const io = r.io;
383 switch (r.mode) {
384 .positional, .positional_reading => {
385 setPosAdjustingBuffer(r, offset);
386 },
387 .streaming, .streaming_reading => {
388 if (offset >= r.pos) return Reader.seekBy(r, @intCast(offset - r.pos));
389 if (r.seek_err) |err| return err;
390 io.vtable.fileSeekTo(io.userdata, r.file, offset) catch |err| {
391 r.seek_err = err;
392 return err;
393 };
394 setPosAdjustingBuffer(r, offset);
395 },
396 .failure => return r.seek_err.?,
397 }
398 }
399
400 pub fn logicalPos(r: *const Reader) u64 {
401 return r.pos - r.interface.bufferedLen();
402 }
403
404 fn setPosAdjustingBuffer(r: *Reader, offset: u64) void {
405 const logical_pos = logicalPos(r);
406 if (offset < logical_pos or offset >= r.pos) {
407 r.interface.seek = 0;
408 r.interface.end = 0;
409 r.pos = offset;
410 } else {
411 const logical_delta: usize = @intCast(offset - logical_pos);
412 r.interface.seek += logical_delta;
413 }
414 }
415
416 /// Number of slices to store on the stack, when trying to send as many byte
417 /// vectors through the underlying read calls as possible.
418 const max_buffers_len = 16;
419
420 fn stream(io_reader: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
421 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
422 switch (r.mode) {
423 .positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) {
424 error.Unimplemented => {
425 r.mode = r.mode.toReading();
426 return 0;
427 },
428 else => |e| return e,
429 },
430 .positional_reading => {
431 const dest = limit.slice(try w.writableSliceGreedy(1));
432 var data: [1][]u8 = .{dest};
433 const n = try readVecPositional(r, &data);
434 w.advance(n);
435 return n;
436 },
437 .streaming_reading => {
438 const dest = limit.slice(try w.writableSliceGreedy(1));
439 var data: [1][]u8 = .{dest};
440 const n = try readVecStreaming(r, &data);
441 w.advance(n);
442 return n;
443 },
444 .failure => return error.ReadFailed,
445 }
446 }
447
448 fn readVec(io_reader: *Io.Reader, data: [][]u8) Io.Reader.Error!usize {
449 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
450 switch (r.mode) {
451 .positional, .positional_reading => return readVecPositional(r, data),
452 .streaming, .streaming_reading => return readVecStreaming(r, data),
453 .failure => return error.ReadFailed,
454 }
455 }
456
457 fn readVecPositional(r: *Reader, data: [][]u8) Io.Reader.Error!usize {
458 const io = r.io;
459 assert(r.interface.bufferedLen() == 0);
460 var iovecs_buffer: [max_buffers_len][]u8 = undefined;
461 const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data);
462 const dest = iovecs_buffer[0..dest_n];
463 assert(dest[0].len > 0);
464 const n = io.vtable.fileReadPositional(io.userdata, r.file, dest, r.pos) catch |err| switch (err) {
465 error.Unseekable => {
466 r.mode = r.mode.toStreaming();
467 const pos = r.pos;
468 if (pos != 0) {
469 r.pos = 0;
470 r.seekBy(@intCast(pos)) catch {
471 r.mode = .failure;
472 return error.ReadFailed;
473 };
474 }
475 return 0;
476 },
477 else => |e| {
478 r.err = e;
479 return error.ReadFailed;
480 },
481 };
482 if (n == 0) {
483 r.size = r.pos;
484 return error.EndOfStream;
485 }
486 r.pos += n;
487 if (n > data_size) {
488 r.interface.end += n - data_size;
489 return data_size;
490 }
491 return n;
492 }
493
494 fn readVecStreaming(r: *Reader, data: [][]u8) Io.Reader.Error!usize {
495 const io = r.io;
496 var iovecs_buffer: [max_buffers_len][]u8 = undefined;
497 const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data);
498 const dest = iovecs_buffer[0..dest_n];
499 assert(dest[0].len > 0);
500 const n = io.vtable.fileReadStreaming(io.userdata, r.file, dest) catch |err| {
501 r.err = err;
502 return error.ReadFailed;
503 };
504 if (n == 0) {
505 r.size = r.pos;
506 return error.EndOfStream;
507 }
508 r.pos += n;
509 if (n > data_size) {
510 r.interface.end += n - data_size;
511 return data_size;
512 }
513 return n;
514 }
515
516 fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
517 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
518 const io = r.io;
519 const file = r.file;
520 const pos = r.pos;
521 switch (r.mode) {
522 .positional, .positional_reading => {
523 const size = r.getSize() catch {
524 r.mode = r.mode.toStreaming();
525 return 0;
526 };
527 const delta = @min(@intFromEnum(limit), size - pos);
528 r.pos = pos + delta;
529 return delta;
530 },
531 .streaming, .streaming_reading => {
532 const size = r.getSize() catch return 0;
533 const n = @min(size - pos, std.math.maxInt(i64), @intFromEnum(limit));
534 io.vtable.fileSeekBy(io.userdata, file, n) catch |err| {
535 r.seek_err = err;
536 return 0;
537 };
538 r.pos = pos + n;
539 return n;
540 },
541 .failure => return error.ReadFailed,
542 }
543 }
544
545 pub fn atEnd(r: *Reader) bool {
546 // Even if stat fails, size is set when end is encountered.
547 const size = r.size orelse return false;
548 return size - r.pos == 0;
549 }
550};
lib/std/Io/ThreadPool.zig+259-12
...@@ -1,11 +1,16 @@...@@ -1,11 +1,16 @@
1const Pool = @This();
2
1const builtin = @import("builtin");3const builtin = @import("builtin");
4const native_os = builtin.os.tag;
5const is_windows = native_os == .windows;
6const windows = std.os.windows;
7
2const std = @import("../std.zig");8const std = @import("../std.zig");
3const Allocator = std.mem.Allocator;9const Allocator = std.mem.Allocator;
4const assert = std.debug.assert;10const assert = std.debug.assert;
5const WaitGroup = std.Thread.WaitGroup;11const WaitGroup = std.Thread.WaitGroup;
6const posix = std.posix;12const posix = std.posix;
7const Io = std.Io;13const Io = std.Io;
8const Pool = @This();
914
10/// Thread-safe.15/// Thread-safe.
11allocator: Allocator,16allocator: Allocator,
...@@ -23,6 +28,10 @@ threadlocal var current_closure: ?*AsyncClosure = null;...@@ -23,6 +28,10 @@ threadlocal var current_closure: ?*AsyncClosure = null;
23const max_iovecs_len = 8;28const max_iovecs_len = 8;
24const splat_buffer_size = 64;29const splat_buffer_size = 64;
2530
31comptime {
32 assert(max_iovecs_len <= posix.IOV_MAX);
33}
34
26pub const Runnable = struct {35pub const Runnable = struct {
27 start: Start,36 start: Start,
28 node: std.SinglyLinkedList.Node = .{},37 node: std.SinglyLinkedList.Node = .{},
...@@ -104,10 +113,13 @@ pub fn io(pool: *Pool) Io {...@@ -104,10 +113,13 @@ pub fn io(pool: *Pool) Io {
104 .conditionWake = conditionWake,113 .conditionWake = conditionWake,
105114
106 .createFile = createFile,115 .createFile = createFile,
107 .openFile = openFile,116 .fileOpen = fileOpen,
108 .closeFile = closeFile,117 .fileClose = fileClose,
109 .pread = pread,
110 .pwrite = pwrite,118 .pwrite = pwrite,
119 .fileReadStreaming = fileReadStreaming,
120 .fileReadPositional = fileReadPositional,
121 .fileSeekBy = fileSeekBy,
122 .fileSeekTo = fileSeekTo,
111123
112 .now = now,124 .now = now,
113 .sleep = sleep,125 .sleep = sleep,
...@@ -631,7 +643,7 @@ fn createFile(...@@ -631,7 +643,7 @@ fn createFile(
631 return .{ .handle = fs_file.handle };643 return .{ .handle = fs_file.handle };
632}644}
633645
634fn openFile(646fn fileOpen(
635 userdata: ?*anyopaque,647 userdata: ?*anyopaque,
636 dir: Io.Dir,648 dir: Io.Dir,
637 sub_path: []const u8,649 sub_path: []const u8,
...@@ -644,21 +656,256 @@ fn openFile(...@@ -644,21 +656,256 @@ fn openFile(
644 return .{ .handle = fs_file.handle };656 return .{ .handle = fs_file.handle };
645}657}
646658
647fn closeFile(userdata: ?*anyopaque, file: Io.File) void {659fn fileClose(userdata: ?*anyopaque, file: Io.File) void {
648 const pool: *Pool = @ptrCast(@alignCast(userdata));660 const pool: *Pool = @ptrCast(@alignCast(userdata));
649 _ = pool;661 _ = pool;
650 const fs_file: std.fs.File = .{ .handle = file.handle };662 const fs_file: std.fs.File = .{ .handle = file.handle };
651 return fs_file.close();663 return fs_file.close();
652}664}
653665
654fn pread(userdata: ?*anyopaque, file: Io.File, buffer: []u8, offset: posix.off_t) Io.File.PReadError!usize {666fn fileReadStreaming(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File.ReadStreamingError!usize {
655 const pool: *Pool = @ptrCast(@alignCast(userdata));667 const pool: *Pool = @ptrCast(@alignCast(userdata));
656 try pool.checkCancel();668
657 const fs_file: std.fs.File = .{ .handle = file.handle };669 if (is_windows) {
658 return switch (offset) {670 const DWORD = windows.DWORD;
659 -1 => fs_file.read(buffer),671 var index: usize = 0;
660 else => fs_file.pread(buffer, @bitCast(offset)),672 var truncate: usize = 0;
673 var total: usize = 0;
674 while (index < data.len) {
675 try pool.checkCancel();
676 {
677 const untruncated = data[index];
678 data[index] = untruncated[truncate..];
679 defer data[index] = untruncated;
680 const buffer = data[index..];
681 const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len);
682 var n: DWORD = undefined;
683 if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) == 0) {
684 switch (windows.GetLastError()) {
685 .IO_PENDING => unreachable,
686 .OPERATION_ABORTED => continue,
687 .BROKEN_PIPE => return 0,
688 .HANDLE_EOF => return 0,
689 .NETNAME_DELETED => return error.ConnectionResetByPeer,
690 .LOCK_VIOLATION => return error.LockViolation,
691 .ACCESS_DENIED => return error.AccessDenied,
692 .INVALID_HANDLE => return error.NotOpenForReading,
693 else => |err| return windows.unexpectedError(err),
694 }
695 }
696 total += n;
697 truncate += n;
698 }
699 while (index < data.len and truncate >= data[index].len) {
700 truncate -= data[index].len;
701 index += 1;
702 }
703 }
704 return total;
705 }
706
707 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
708 var i: usize = 0;
709 for (data) |buf| {
710 if (iovecs_buffer.len - i == 0) break;
711 if (buf.len != 0) {
712 iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len };
713 i += 1;
714 }
715 }
716 const dest = iovecs_buffer[0..i];
717 assert(dest[0].len > 0);
718
719 if (native_os == .wasi and !builtin.link_libc) {
720 try pool.checkCancel();
721 var nread: usize = undefined;
722 switch (std.os.wasi.fd_read(file.handle, dest.ptr, dest.len, &nread)) {
723 .SUCCESS => return nread,
724 .INTR => unreachable,
725 .INVAL => unreachable,
726 .FAULT => unreachable,
727 .AGAIN => unreachable, // currently not support in WASI
728 .BADF => return error.NotOpenForReading, // can be a race condition
729 .IO => return error.InputOutput,
730 .ISDIR => return error.IsDir,
731 .NOBUFS => return error.SystemResources,
732 .NOMEM => return error.SystemResources,
733 .NOTCONN => return error.SocketNotConnected,
734 .CONNRESET => return error.ConnectionResetByPeer,
735 .TIMEDOUT => return error.ConnectionTimedOut,
736 .NOTCAPABLE => return error.AccessDenied,
737 else => |err| return posix.unexpectedErrno(err),
738 }
739 }
740
741 while (true) {
742 try pool.checkCancel();
743 const rc = posix.system.readv(file.handle, dest.ptr, dest.len);
744 switch (posix.errno(rc)) {
745 .SUCCESS => return @intCast(rc),
746 .INTR => continue,
747 .INVAL => unreachable,
748 .FAULT => unreachable,
749 .SRCH => return error.ProcessNotFound,
750 .AGAIN => return error.WouldBlock,
751 .BADF => return error.NotOpenForReading, // can be a race condition
752 .IO => return error.InputOutput,
753 .ISDIR => return error.IsDir,
754 .NOBUFS => return error.SystemResources,
755 .NOMEM => return error.SystemResources,
756 .NOTCONN => return error.SocketNotConnected,
757 .CONNRESET => return error.ConnectionResetByPeer,
758 .TIMEDOUT => return error.ConnectionTimedOut,
759 else => |err| return posix.unexpectedErrno(err),
760 }
761 }
762}
763
764fn fileReadPositional(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset: u64) Io.File.ReadPositionalError!usize {
765 const pool: *Pool = @ptrCast(@alignCast(userdata));
766
767 const have_pread_but_not_preadv = switch (native_os) {
768 .windows, .macos, .ios, .watchos, .tvos, .visionos, .haiku, .serenity => true,
769 else => false,
661 };770 };
771 if (have_pread_but_not_preadv) {
772 @compileError("TODO");
773 }
774
775 if (is_windows) {
776 const DWORD = windows.DWORD;
777 const OVERLAPPED = windows.OVERLAPPED;
778 var index: usize = 0;
779 var truncate: usize = 0;
780 var total: usize = 0;
781 while (true) {
782 try pool.checkCancel();
783 {
784 const untruncated = data[index];
785 data[index] = untruncated[truncate..];
786 defer data[index] = untruncated;
787 const buffer = data[index..];
788 const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len);
789 var n: DWORD = undefined;
790 var overlapped_data: OVERLAPPED = undefined;
791 const overlapped: ?*OVERLAPPED = if (offset) |off| blk: {
792 overlapped_data = .{
793 .Internal = 0,
794 .InternalHigh = 0,
795 .DUMMYUNIONNAME = .{
796 .DUMMYSTRUCTNAME = .{
797 .Offset = @as(u32, @truncate(off)),
798 .OffsetHigh = @as(u32, @truncate(off >> 32)),
799 },
800 },
801 .hEvent = null,
802 };
803 break :blk &overlapped_data;
804 } else null;
805 if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, overlapped) == 0) {
806 switch (windows.GetLastError()) {
807 .IO_PENDING => unreachable,
808 .OPERATION_ABORTED => continue,
809 .BROKEN_PIPE => return 0,
810 .HANDLE_EOF => return 0,
811 .NETNAME_DELETED => return error.ConnectionResetByPeer,
812 .LOCK_VIOLATION => return error.LockViolation,
813 .ACCESS_DENIED => return error.AccessDenied,
814 .INVALID_HANDLE => return error.NotOpenForReading,
815 else => |err| return windows.unexpectedError(err),
816 }
817 }
818 total += n;
819 truncate += n;
820 }
821 while (index < data.len and truncate >= data[index].len) {
822 truncate -= data[index].len;
823 index += 1;
824 }
825 }
826 return total;
827 }
828
829 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
830 var i: usize = 0;
831 for (data) |buf| {
832 if (iovecs_buffer.len - i == 0) break;
833 if (buf.len != 0) {
834 iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len };
835 i += 1;
836 }
837 }
838 const dest = iovecs_buffer[0..i];
839 assert(dest[0].len > 0);
840
841 if (native_os == .wasi and !builtin.link_libc) {
842 try pool.checkCancel();
843 var nread: usize = undefined;
844 switch (std.os.wasi.fd_pread(file.handle, dest.ptr, dest.len, offset, &nread)) {
845 .SUCCESS => return nread,
846 .INTR => unreachable,
847 .INVAL => unreachable,
848 .FAULT => unreachable,
849 .AGAIN => unreachable,
850 .BADF => return error.NotOpenForReading, // can be a race condition
851 .IO => return error.InputOutput,
852 .ISDIR => return error.IsDir,
853 .NOBUFS => return error.SystemResources,
854 .NOMEM => return error.SystemResources,
855 .NOTCONN => return error.SocketNotConnected,
856 .CONNRESET => return error.ConnectionResetByPeer,
857 .TIMEDOUT => return error.ConnectionTimedOut,
858 .NXIO => return error.Unseekable,
859 .SPIPE => return error.Unseekable,
860 .OVERFLOW => return error.Unseekable,
861 .NOTCAPABLE => return error.AccessDenied,
862 else => |err| return posix.unexpectedErrno(err),
863 }
864 }
865
866 const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv;
867 while (true) {
868 try pool.checkCancel();
869 const rc = preadv_sym(file.handle, dest.ptr, dest.len, @bitCast(offset));
870 switch (posix.errno(rc)) {
871 .SUCCESS => return @bitCast(rc),
872 .INTR => continue,
873 .INVAL => unreachable,
874 .FAULT => unreachable,
875 .SRCH => return error.ProcessNotFound,
876 .AGAIN => return error.WouldBlock,
877 .BADF => return error.NotOpenForReading, // can be a race condition
878 .IO => return error.InputOutput,
879 .ISDIR => return error.IsDir,
880 .NOBUFS => return error.SystemResources,
881 .NOMEM => return error.SystemResources,
882 .NOTCONN => return error.SocketNotConnected,
883 .CONNRESET => return error.ConnectionResetByPeer,
884 .TIMEDOUT => return error.ConnectionTimedOut,
885 .NXIO => return error.Unseekable,
886 .SPIPE => return error.Unseekable,
887 .OVERFLOW => return error.Unseekable,
888 else => |err| return posix.unexpectedErrno(err),
889 }
890 }
891}
892
893fn fileSeekBy(userdata: ?*anyopaque, file: Io.File, offset: i64) Io.File.SeekError!void {
894 const pool: *Pool = @ptrCast(@alignCast(userdata));
895 try pool.checkCancel();
896
897 _ = file;
898 _ = offset;
899 @panic("TODO");
900}
901
902fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekError!void {
903 const pool: *Pool = @ptrCast(@alignCast(userdata));
904 try pool.checkCancel();
905
906 _ = file;
907 _ = offset;
908 @panic("TODO");
662}909}
663910
664fn pwrite(userdata: ?*anyopaque, file: Io.File, buffer: []const u8, offset: posix.off_t) Io.File.PWriteError!usize {911fn pwrite(userdata: ?*anyopaque, file: Io.File, buffer: []const u8, offset: posix.off_t) Io.File.PWriteError!usize {
lib/std/Io/Writer.zig+3-2
...@@ -5,7 +5,7 @@ const Writer = @This();...@@ -5,7 +5,7 @@ const Writer = @This();
5const std = @import("../std.zig");5const std = @import("../std.zig");
6const assert = std.debug.assert;6const assert = std.debug.assert;
7const Limit = std.Io.Limit;7const Limit = std.Io.Limit;
8const File = std.fs.File;8const File = std.Io.File;
9const testing = std.testing;9const testing = std.testing;
10const Allocator = std.mem.Allocator;10const Allocator = std.mem.Allocator;
11const ArrayList = std.ArrayList;11const ArrayList = std.ArrayList;
...@@ -2778,7 +2778,8 @@ pub const Allocating = struct {...@@ -2778,7 +2778,8 @@ pub const Allocating = struct {
2778 if (additional == 0) return error.EndOfStream;2778 if (additional == 0) return error.EndOfStream;
2779 a.ensureUnusedCapacity(limit.minInt64(additional)) catch return error.WriteFailed;2779 a.ensureUnusedCapacity(limit.minInt64(additional)) catch return error.WriteFailed;
2780 const dest = limit.slice(a.writer.buffer[a.writer.end..]);2780 const dest = limit.slice(a.writer.buffer[a.writer.end..]);
2781 const n = try file_reader.read(dest);2781 const n = try file_reader.interface.readSliceShort(dest);
2782 if (n == 0) return error.EndOfStream;
2782 a.writer.end += n;2783 a.writer.end += n;
2783 return n;2784 return n;
2784 }2785 }
lib/std/Io/net.zig+52-32
...@@ -17,9 +17,12 @@ pub const ListenOptions = struct {...@@ -17,9 +17,12 @@ pub const ListenOptions = struct {
17 force_nonblocking: bool = false,17 force_nonblocking: bool = false,
18};18};
1919
20/// An already-validated host name.20/// An already-validated host name. A valid host name:
21/// * Has length less than or equal to `max_len`.
22/// * Is valid UTF-8.
23/// * Lacks ASCII characters other than alphanumeric, '-', and '.'.
21pub const HostName = struct {24pub const HostName = struct {
22 /// Externally managed memory. Already checked to be within `max_len`.25 /// Externally managed memory. Already checked to be valid.
23 bytes: []const u8,26 bytes: []const u8,
2427
25 pub const max_len = 255;28 pub const max_len = 255;
...@@ -55,13 +58,14 @@ pub const HostName = struct {...@@ -55,13 +58,14 @@ pub const HostName = struct {
55 family: ?IpAddress.Tag = null,58 family: ?IpAddress.Tag = null,
56 };59 };
5760
58 pub const LookupError = Io.Cancelable || error{};61 pub const LookupError = Io.Cancelable || Io.File.OpenError || Io.File.Reader.Error || error{
62 UnknownHostName,
63 };
5964
60 pub const LookupResult = struct {65 pub const LookupResult = struct {
61 /// How many `LookupOptions.addresses_buffer` elements are populated.66 /// How many `LookupOptions.addresses_buffer` elements are populated.
62 addresses_len: usize,67 addresses_len: usize = 0,
63 /// Length zero means no canonical name returned.68 canonical_name: ?HostName = null,
64 canonical_name_len: usize,
65 };69 };
6670
67 pub fn lookup(host_name: HostName, io: Io, options: LookupOptions) LookupError!LookupResult {71 pub fn lookup(host_name: HostName, io: Io, options: LookupOptions) LookupError!LookupResult {
...@@ -75,17 +79,17 @@ pub const HostName = struct {...@@ -75,17 +79,17 @@ pub const HostName = struct {
75 if (options.family != .ip6) {79 if (options.family != .ip6) {
76 if (IpAddress.parseIp4(name, options.port)) |addr| {80 if (IpAddress.parseIp4(name, options.port)) |addr| {
77 options.addresses_buffer[0] = addr;81 options.addresses_buffer[0] = addr;
78 return .{ .addresses_len = 1, .canonical_name_len = 0 };82 return .{ .addresses_len = 1 };
79 } else |_| {}83 } else |_| {}
80 }84 }
81 if (options.family != .ip4) {85 if (options.family != .ip4) {
82 if (IpAddress.parseIp6(name, options.port)) |addr| {86 if (IpAddress.parseIp6(name, options.port)) |addr| {
83 options.addresses_buffer[0] = addr;87 options.addresses_buffer[0] = addr;
84 return .{ .addresses_len = 1, .canonical_name_len = 0 };88 return .{ .addresses_len = 1 };
85 } else |_| {}89 } else |_| {}
86 }90 }
87 {91 {
88 const result = try lookupHosts(io, options);92 const result = try lookupHosts(host_name, io, options);
89 if (result.addresses_len > 0) return sortLookupResults(options, result);93 if (result.addresses_len > 0) return sortLookupResults(options, result);
90 }94 }
91 {95 {
...@@ -110,8 +114,12 @@ pub const HostName = struct {...@@ -110,8 +114,12 @@ pub const HostName = struct {
110 i += 1;114 i += 1;
111 }115 }
112 const canon_name = "localhost";116 const canon_name = "localhost";
113 options.canonical_name_buffer[0..canon_name.len].* = canon_name.*;117 const canon_name_dest = options.canonical_name_buffer[0..canon_name.len];
114 return sortLookupResults(options, .{ .addresses_len = i, .canonical_name_len = canon_name.len });118 canon_name_dest.* = canon_name.*;
119 return sortLookupResults(options, .{
120 .addresses_len = i,
121 .canonical_name = .{ .bytes = canon_name_dest },
122 });
115 }123 }
116 }124 }
117 {125 {
...@@ -135,27 +143,27 @@ pub const HostName = struct {...@@ -135,27 +143,27 @@ pub const HostName = struct {
135 @panic("TODO");143 @panic("TODO");
136 }144 }
137145
138 fn lookupHosts(io: Io, options: LookupOptions) !LookupResult {146 fn lookupHosts(host_name: HostName, io: Io, options: LookupOptions) !LookupResult {
139 const file = Io.File.openFileAbsoluteZ(io, "/etc/hosts", .{}) catch |err| switch (err) {147 const file = Io.File.openAbsolute(io, "/etc/hosts", .{}) catch |err| switch (err) {
140 error.FileNotFound,148 error.FileNotFound,
141 error.NotDir,149 error.NotDir,
142 error.AccessDenied,150 error.AccessDenied,
143 => return,151 => return .{},
152
144 else => |e| return e,153 else => |e| return e,
145 };154 };
146 defer file.close();155 defer file.close(io);
147156
148 var line_buf: [512]u8 = undefined;157 var line_buf: [512]u8 = undefined;
149 var file_reader = file.reader(io, &line_buf);158 var file_reader = file.reader(io, &line_buf);
150 return lookupHostsReader(options, &file_reader.interface) catch |err| switch (err) {159 return lookupHostsReader(host_name, options, &file_reader.interface) catch |err| switch (err) {
151 error.OutOfMemory => return error.OutOfMemory,
152 error.ReadFailed => return file_reader.err.?,160 error.ReadFailed => return file_reader.err.?,
153 };161 };
154 }162 }
155163
156 fn lookupHostsReader(options: LookupOptions, reader: *Io.Reader) error{ReadFailed}!LookupResult {164 fn lookupHostsReader(host_name: HostName, options: LookupOptions, reader: *Io.Reader) error{ReadFailed}!LookupResult {
157 var addresses_len: usize = 0;165 var addresses_len: usize = 0;
158 var canonical_name_len: usize = 0;166 var canonical_name: ?HostName = null;
159 while (true) {167 while (true) {
160 const line = reader.takeDelimiterExclusive('\n') catch |err| switch (err) {168 const line = reader.takeDelimiterExclusive('\n') catch |err| switch (err) {
161 error.StreamTooLong => {169 error.StreamTooLong => {
...@@ -176,19 +184,20 @@ pub const HostName = struct {...@@ -176,19 +184,20 @@ pub const HostName = struct {
176 const ip_text = line_it.next() orelse continue;184 const ip_text = line_it.next() orelse continue;
177 var first_name_text: ?[]const u8 = null;185 var first_name_text: ?[]const u8 = null;
178 while (line_it.next()) |name_text| {186 while (line_it.next()) |name_text| {
179 if (std.mem.eql(u8, name_text, options.name)) {187 if (std.mem.eql(u8, name_text, host_name.bytes)) {
180 if (first_name_text == null) first_name_text = name_text;188 if (first_name_text == null) first_name_text = name_text;
181 break;189 break;
182 }190 }
183 } else continue;191 } else continue;
184192
185 if (canonical_name_len == 0) {193 if (canonical_name == null) {
186 if (HostName.init(first_name_text)) |name_text| {194 if (HostName.init(first_name_text.?)) |name_text| {
187 if (name_text.len <= options.canonical_name_buffer.len) {195 if (name_text.bytes.len <= options.canonical_name_buffer.len) {
188 @memcpy(options.canonical_name_buffer[0..name_text.len], name_text);196 const canonical_name_dest = options.canonical_name_buffer[0..name_text.bytes.len];
189 canonical_name_len = name_text.len;197 @memcpy(canonical_name_dest, name_text.bytes);
198 canonical_name = .{ .bytes = canonical_name_dest };
190 }199 }
191 }200 } else |_| {}
192 }201 }
193202
194 if (options.family != .ip6) {203 if (options.family != .ip6) {
...@@ -197,7 +206,7 @@ pub const HostName = struct {...@@ -197,7 +206,7 @@ pub const HostName = struct {
197 addresses_len += 1;206 addresses_len += 1;
198 if (options.addresses_buffer.len - addresses_len == 0) return .{207 if (options.addresses_buffer.len - addresses_len == 0) return .{
199 .addresses_len = addresses_len,208 .addresses_len = addresses_len,
200 .canonical_name_len = canonical_name_len,209 .canonical_name = canonical_name,
201 };210 };
202 } else |_| {}211 } else |_| {}
203 }212 }
...@@ -207,11 +216,15 @@ pub const HostName = struct {...@@ -207,11 +216,15 @@ pub const HostName = struct {
207 addresses_len += 1;216 addresses_len += 1;
208 if (options.addresses_buffer.len - addresses_len == 0) return .{217 if (options.addresses_buffer.len - addresses_len == 0) return .{
209 .addresses_len = addresses_len,218 .addresses_len = addresses_len,
210 .canonical_name_len = canonical_name_len,219 .canonical_name = canonical_name,
211 };220 };
212 } else |_| {}221 } else |_| {}
213 }222 }
214 }223 }
224 return .{
225 .addresses_len = addresses_len,
226 .canonical_name = canonical_name,
227 };
215 }228 }
216229
217 pub const ConnectTcpError = LookupError || IpAddress.ConnectTcpError;230 pub const ConnectTcpError = LookupError || IpAddress.ConnectTcpError;
...@@ -289,9 +302,9 @@ pub const IpAddress = union(enum) {...@@ -289,9 +302,9 @@ pub const IpAddress = union(enum) {
289 }302 }
290 }303 }
291304
292 pub fn format(a: IpAddress, w: *std.io.Writer) std.io.Writer.Error!void {305 pub fn format(a: IpAddress, w: *Io.Writer) Io.Writer.Error!void {
293 switch (a) {306 switch (a) {
294 .ip4, .ip6 => |x| return x.format(w),307 inline .ip4, .ip6 => |x| return x.format(w),
295 }308 }
296 }309 }
297310
...@@ -365,7 +378,7 @@ pub const Ip4Address = struct {...@@ -365,7 +378,7 @@ pub const Ip4Address = struct {
365 return error.Incomplete;378 return error.Incomplete;
366 }379 }
367380
368 pub fn format(a: Ip4Address, w: *std.io.Writer) std.io.Writer.Error!void {381 pub fn format(a: Ip4Address, w: *Io.Writer) Io.Writer.Error!void {
369 const bytes = &a.bytes;382 const bytes = &a.bytes;
370 try w.print("{d}.{d}.{d}.{d}:{d}", .{ bytes[0], bytes[1], bytes[2], bytes[3], a.port });383 try w.print("{d}.{d}.{d}.{d}:{d}", .{ bytes[0], bytes[1], bytes[2], bytes[3], a.port });
371 }384 }
...@@ -393,6 +406,13 @@ pub const Ip6Address = struct {...@@ -393,6 +406,13 @@ pub const Ip6Address = struct {
393 Incomplete,406 Incomplete,
394 };407 };
395408
409 pub fn localhost(port: u16) Ip6Address {
410 return .{
411 .bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
412 .port = port,
413 };
414 }
415
396 pub fn parse(buffer: []const u8, port: u16) ParseError!Ip6Address {416 pub fn parse(buffer: []const u8, port: u16) ParseError!Ip6Address {
397 var result: Ip6Address = .{417 var result: Ip6Address = .{
398 .port = port,418 .port = port,
...@@ -504,7 +524,7 @@ pub const Ip6Address = struct {...@@ -504,7 +524,7 @@ pub const Ip6Address = struct {
504 }524 }
505 }525 }
506526
507 pub fn format(a: Ip6Address, w: *std.io.Writer) std.io.Writer.Error!void {527 pub fn format(a: Ip6Address, w: *Io.Writer) Io.Writer.Error!void {
508 const bytes = &a.bytes;528 const bytes = &a.bytes;
509 if (std.mem.eql(u8, bytes[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) {529 if (std.mem.eql(u8, bytes[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) {
510 try w.print("[::ffff:{d}.{d}.{d}.{d}]:{d}", .{530 try w.print("[::ffff:{d}.{d}.{d}.{d}]:{d}", .{
lib/std/fs/File.zig+7-124
...@@ -17,25 +17,12 @@ const Alignment = std.mem.Alignment;...@@ -17,25 +17,12 @@ const Alignment = std.mem.Alignment;
17/// The OS-specific file descriptor or file handle.17/// The OS-specific file descriptor or file handle.
18handle: Handle,18handle: Handle,
1919
20pub const Handle = posix.fd_t;20pub const Handle = std.Io.File.Handle;
21pub const Mode = posix.mode_t;21pub const Mode = std.Io.File.Mode;
22pub const INode = posix.ino_t;22pub const INode = std.Io.File.INode;
23pub const Uid = posix.uid_t;23pub const Uid = posix.uid_t;
24pub const Gid = posix.gid_t;24pub const Gid = posix.gid_t;
2525pub const Kind = std.Io.File.Kind;
26pub const Kind = enum {
27 block_device,
28 character_device,
29 directory,
30 named_pipe,
31 sym_link,
32 file,
33 unix_domain_socket,
34 whiteout,
35 door,
36 event_port,
37 unknown,
38};
3926
40/// This is the default mode given to POSIX operating systems for creating27/// This is the default mode given to POSIX operating systems for creating
41/// files. `0o666` is "-rw-rw-rw-" which is counter-intuitive at first,28/// files. `0o666` is "-rw-rw-rw-" which is counter-intuitive at first,
...@@ -399,115 +386,11 @@ pub fn mode(self: File) ModeError!Mode {...@@ -399,115 +386,11 @@ pub fn mode(self: File) ModeError!Mode {
399 return (try self.stat()).mode;386 return (try self.stat()).mode;
400}387}
401388
402pub const Stat = struct {389pub const Stat = std.Io.File.Stat;
403 /// A number that the system uses to point to the file metadata. This
404 /// number is not guaranteed to be unique across time, as some file
405 /// systems may reuse an inode after its file has been deleted. Some
406 /// systems may change the inode of a file over time.
407 ///
408 /// On Linux, the inode is a structure that stores the metadata, and
409 /// the inode _number_ is what you see here: the index number of the
410 /// inode.
411 ///
412 /// The FileIndex on Windows is similar. It is a number for a file that
413 /// is unique to each filesystem.
414 inode: INode,
415 size: u64,
416 /// This is available on POSIX systems and is always 0 otherwise.
417 mode: Mode,
418 kind: Kind,
419
420 /// Last access time in nanoseconds, relative to UTC 1970-01-01.
421 atime: i128,
422 /// Last modification time in nanoseconds, relative to UTC 1970-01-01.
423 mtime: i128,
424 /// Last status/metadata change time in nanoseconds, relative to UTC 1970-01-01.
425 ctime: i128,
426
427 pub fn fromPosix(st: posix.Stat) Stat {
428 const atime = st.atime();
429 const mtime = st.mtime();
430 const ctime = st.ctime();
431 return .{
432 .inode = st.ino,
433 .size = @bitCast(st.size),
434 .mode = st.mode,
435 .kind = k: {
436 const m = st.mode & posix.S.IFMT;
437 switch (m) {
438 posix.S.IFBLK => break :k .block_device,
439 posix.S.IFCHR => break :k .character_device,
440 posix.S.IFDIR => break :k .directory,
441 posix.S.IFIFO => break :k .named_pipe,
442 posix.S.IFLNK => break :k .sym_link,
443 posix.S.IFREG => break :k .file,
444 posix.S.IFSOCK => break :k .unix_domain_socket,
445 else => {},
446 }
447 if (builtin.os.tag.isSolarish()) switch (m) {
448 posix.S.IFDOOR => break :k .door,
449 posix.S.IFPORT => break :k .event_port,
450 else => {},
451 };
452
453 break :k .unknown;
454 },
455 .atime = @as(i128, atime.sec) * std.time.ns_per_s + atime.nsec,
456 .mtime = @as(i128, mtime.sec) * std.time.ns_per_s + mtime.nsec,
457 .ctime = @as(i128, ctime.sec) * std.time.ns_per_s + ctime.nsec,
458 };
459 }
460
461 pub fn fromLinux(stx: linux.Statx) Stat {
462 const atime = stx.atime;
463 const mtime = stx.mtime;
464 const ctime = stx.ctime;
465
466 return .{
467 .inode = stx.ino,
468 .size = stx.size,
469 .mode = stx.mode,
470 .kind = switch (stx.mode & linux.S.IFMT) {
471 linux.S.IFDIR => .directory,
472 linux.S.IFCHR => .character_device,
473 linux.S.IFBLK => .block_device,
474 linux.S.IFREG => .file,
475 linux.S.IFIFO => .named_pipe,
476 linux.S.IFLNK => .sym_link,
477 linux.S.IFSOCK => .unix_domain_socket,
478 else => .unknown,
479 },
480 .atime = @as(i128, atime.sec) * std.time.ns_per_s + atime.nsec,
481 .mtime = @as(i128, mtime.sec) * std.time.ns_per_s + mtime.nsec,
482 .ctime = @as(i128, ctime.sec) * std.time.ns_per_s + ctime.nsec,
483 };
484 }
485
486 pub fn fromWasi(st: std.os.wasi.filestat_t) Stat {
487 return .{
488 .inode = st.ino,
489 .size = @bitCast(st.size),
490 .mode = 0,
491 .kind = switch (st.filetype) {
492 .BLOCK_DEVICE => .block_device,
493 .CHARACTER_DEVICE => .character_device,
494 .DIRECTORY => .directory,
495 .SYMBOLIC_LINK => .sym_link,
496 .REGULAR_FILE => .file,
497 .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
498 else => .unknown,
499 },
500 .atime = st.atim,
501 .mtime = st.mtim,
502 .ctime = st.ctim,
503 };
504 }
505};
506390
507pub const StatError = posix.FStatError;391pub const StatError = posix.FStatError;
508392
509/// Returns `Stat` containing basic information about the `File`.393/// Returns `Stat` containing basic information about the `File`.
510/// TODO: integrate with async I/O
511pub fn stat(self: File) StatError!Stat {394pub fn stat(self: File) StatError!Stat {
512 if (builtin.os.tag == .windows) {395 if (builtin.os.tag == .windows) {
513 var io_status_block: windows.IO_STATUS_BLOCK = undefined;396 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
...@@ -1727,7 +1610,7 @@ pub const Writer = struct {...@@ -1727,7 +1610,7 @@ pub const Writer = struct {
17271610
1728 pub fn sendFile(1611 pub fn sendFile(
1729 io_w: *std.Io.Writer,1612 io_w: *std.Io.Writer,
1730 file_reader: *Reader,1613 file_reader: *std.Io.File.Reader,
1731 limit: std.Io.Limit,1614 limit: std.Io.Limit,
1732 ) std.Io.Writer.FileError!usize {1615 ) std.Io.Writer.FileError!usize {
1733 const reader_buffered = file_reader.interface.buffered();1616 const reader_buffered = file_reader.interface.buffered();
...@@ -1994,7 +1877,7 @@ pub const Writer = struct {...@@ -1994,7 +1877,7 @@ pub const Writer = struct {
19941877
1995 fn sendFileBuffered(1878 fn sendFileBuffered(
1996 io_w: *std.Io.Writer,1879 io_w: *std.Io.Writer,
1997 file_reader: *Reader,1880 file_reader: *std.Io.File.Reader,
1998 reader_buffered: []const u8,1881 reader_buffered: []const u8,
1999 ) std.Io.Writer.FileError!usize {1882 ) std.Io.Writer.FileError!usize {
2000 const n = try drain(io_w, &.{reader_buffered}, 1);1883 const n = try drain(io_w, &.{reader_buffered}, 1);
lib/std/posix.zig+5-51
...@@ -806,36 +806,7 @@ pub fn exit(status: u8) noreturn {...@@ -806,36 +806,7 @@ pub fn exit(status: u8) noreturn {
806 system.exit(status);806 system.exit(status);
807}807}
808808
809pub const ReadError = error{809pub const ReadError = std.Io.File.ReadStreamingError;
810 InputOutput,
811 SystemResources,
812 IsDir,
813 OperationAborted,
814 BrokenPipe,
815 ConnectionResetByPeer,
816 ConnectionTimedOut,
817 NotOpenForReading,
818 SocketNotConnected,
819
820 /// This error occurs when no global event loop is configured,
821 /// and reading from the file descriptor would block.
822 WouldBlock,
823
824 /// reading a timerfd with CANCEL_ON_SET will lead to this error
825 /// when the clock goes through a discontinuous change
826 Canceled,
827
828 /// In WASI, this error occurs when the file descriptor does
829 /// not hold the required rights to read from it.
830 AccessDenied,
831
832 /// This error occurs in Linux if the process to be read from
833 /// no longer exists.
834 ProcessNotFound,
835
836 /// Unable to read file due to lock.
837 LockViolation,
838} || UnexpectedError;
839810
840/// Returns the number of bytes that were read, which can be less than811/// Returns the number of bytes that were read, which can be less than
841/// buf.len. If 0 bytes were read, that means EOF.812/// buf.len. If 0 bytes were read, that means EOF.
...@@ -922,7 +893,6 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {...@@ -922,7 +893,6 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
922/// a pointer within the address space of the application.893/// a pointer within the address space of the application.
923pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {894pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
924 if (native_os == .windows) {895 if (native_os == .windows) {
925 // TODO improve this to use ReadFileScatter
926 if (iov.len == 0) return 0;896 if (iov.len == 0) return 0;
927 const first = iov[0];897 const first = iov[0];
928 return read(fd, first.base[0..first.len]);898 return read(fd, first.base[0..first.len]);
...@@ -970,7 +940,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {...@@ -970,7 +940,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
970 }940 }
971}941}
972942
973pub const PReadError = ReadError || error{Unseekable};943pub const PReadError = std.Io.ReadPositionalError;
974944
975/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.945/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
976///946///
...@@ -5376,13 +5346,7 @@ pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void {...@@ -5376,13 +5346,7 @@ pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void {
5376 }5346 }
5377}5347}
53785348
5379pub const SeekError = error{5349pub const SeekError = std.Io.File.SeekError;
5380 Unseekable,
5381
5382 /// In WASI, this error may occur when the file descriptor does
5383 /// not hold the required rights to seek on it.
5384 AccessDenied,
5385} || UnexpectedError;
53865350
5387/// Repositions read/write file offset relative to the beginning.5351/// Repositions read/write file offset relative to the beginning.
5388pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void {5352pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void {
...@@ -7558,7 +7522,7 @@ pub fn ioctl_SIOCGIFINDEX(fd: fd_t, ifr: *ifreq) IoCtl_SIOCGIFINDEX_Error!void {...@@ -7558,7 +7522,7 @@ pub fn ioctl_SIOCGIFINDEX(fd: fd_t, ifr: *ifreq) IoCtl_SIOCGIFINDEX_Error!void {
7558 }7522 }
7559}7523}
75607524
7561const lfs64_abi = native_os == .linux and builtin.link_libc and (builtin.abi.isGnu() or builtin.abi.isAndroid());7525pub const lfs64_abi = native_os == .linux and builtin.link_libc and (builtin.abi.isGnu() or builtin.abi.isAndroid());
75627526
7563/// Whether or not `error.Unexpected` will print its value and a stack trace.7527/// Whether or not `error.Unexpected` will print its value and a stack trace.
7564///7528///
...@@ -7570,17 +7534,7 @@ pub const unexpected_error_tracing = builtin.mode == .Debug and switch (builtin....@@ -7570,17 +7534,7 @@ pub const unexpected_error_tracing = builtin.mode == .Debug and switch (builtin.
7570 else => false,7534 else => false,
7571};7535};
75727536
7573pub const UnexpectedError = error{7537pub const UnexpectedError = std.Io.UnexpectedError;
7574 /// The Operating System returned an undocumented error code.
7575 ///
7576 /// This error is in theory not possible, but it would be better
7577 /// to handle this error than to invoke undefined behavior.
7578 ///
7579 /// When this error code is observed, it usually means the Zig Standard
7580 /// Library needs a small patch to add the error code to the error set for
7581 /// the respective function.
7582 Unexpected,
7583};
75847538
7585/// Call this when you made a syscall or something that sets errno7539/// Call this when you made a syscall or something that sets errno
7586/// and you get an unexpected error.7540/// and you get an unexpected error.