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;
66const posix = std.posix;
77const math = std.math;
88const assert = std.debug.assert;
9const fs = std.fs;
109const Allocator = std.mem.Allocator;
1110const Alignment = std.mem.Alignment;
1211
......@@ -650,10 +649,15 @@ pub const VTable = struct {
650649 conditionWake: *const fn (?*anyopaque, cond: *Condition, wake: Condition.Wake) void,
651650
652651 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,
654 closeFile: *const fn (?*anyopaque, File) void,
655 pread: *const fn (?*anyopaque, file: File, buffer: []u8, offset: std.posix.off_t) File.PReadError!usize,
652 fileOpen: *const fn (?*anyopaque, dir: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File,
653 fileClose: *const fn (?*anyopaque, File) void,
656654 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
658662 now: *const fn (?*anyopaque, clockid: std.posix.clockid_t) ClockGetTimeError!Timestamp,
659663 sleep: *const fn (?*anyopaque, clockid: std.posix.clockid_t, deadline: Deadline) SleepError!void,
......@@ -670,6 +674,18 @@ pub const Cancelable = error{
670674 Canceled,
671675};
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
673689pub const Dir = struct {
674690 handle: Handle,
675691
......@@ -680,7 +696,7 @@ pub const Dir = struct {
680696 pub const Handle = std.posix.fd_t;
681697
682698 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);
684700 }
685701
686702 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 {
706722 }
707723};
708724
709pub const File = struct {
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};
725pub const File = @import("Io/File.zig");
769726
770727pub const Timestamp = enum(i96) {
771728 _,
lib/std/Io/EventLoop.zig+28-28
......@@ -93,7 +93,7 @@ const Fiber = struct {
9393 }
9494
9595 fn resultPointer(f: *Fiber, comptime Result: type) *Result {
96 return @alignCast(@ptrCast(f.resultBytes(.of(Result))));
96 return @ptrCast(@alignCast(f.resultBytes(.of(Result))));
9797 }
9898
9999 fn resultBytes(f: *Fiber, alignment: Alignment) [*]u8 {
......@@ -153,8 +153,8 @@ pub fn io(el: *EventLoop) Io {
153153 .conditionWake = conditionWake,
154154
155155 .createFile = createFile,
156 .openFile = openFile,
157 .closeFile = closeFile,
156 .fileOpen = fileOpen,
157 .fileClose = fileClose,
158158 .pread = pread,
159159 .pwrite = pwrite,
160160
......@@ -193,7 +193,7 @@ pub fn init(el: *EventLoop, gpa: Allocator) !void {
193193 };
194194 const main_thread = &el.threads.allocated[0];
195195 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));
197197 (idle_stack_end - 1)[0..1].* = .{@intFromPtr(el)};
198198 main_thread.* = .{
199199 .thread = undefined,
......@@ -244,7 +244,7 @@ pub fn deinit(el: *EventLoop) void {
244244 assert(ready_fiber == null or ready_fiber == Fiber.finished); // pending async
245245 }
246246 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));
248248 const idle_stack_end_offset = std.mem.alignForward(usize, el.threads.allocated.len * @sizeOf(Thread) + idle_stack_size, std.heap.page_size_max);
249249 for (el.threads.allocated[1..active_threads]) |*thread| thread.thread.join();
250250 el.gpa.free(allocated_ptr[0..idle_stack_end_offset]);
......@@ -530,7 +530,7 @@ const SwitchMessage = struct {
530530 const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev));
531531 assert(prev_fiber.queue_next == null);
532532 for (futures) |any_future| {
533 const future_fiber: *Fiber = @alignCast(@ptrCast(any_future));
533 const future_fiber: *Fiber = @ptrCast(@alignCast(any_future));
534534 if (@atomicRmw(?*Fiber, &future_fiber.awaiter, .Xchg, prev_fiber, .acq_rel) == Fiber.finished) {
535535 const closure: *AsyncClosure = .fromFiber(future_fiber);
536536 if (!@atomicRmw(bool, &closure.already_awaited, .Xchg, true, .seq_cst)) {
......@@ -897,12 +897,12 @@ fn asyncConcurrent(
897897 assert(result_len <= Fiber.max_result_size); // TODO
898898 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));
901901 const fiber = try Fiber.allocate(event_loop);
902902 std.log.debug("allocated {*}", .{fiber});
903903
904904 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));
906906 (stack_end - 1)[0..1].* = .{@intFromPtr(&AsyncClosure.call)};
907907 fiber.* = .{
908908 .required_align = {},
......@@ -974,7 +974,7 @@ fn asyncDetached(
974974 assert(context_alignment.compare(.lte, Fiber.max_context_align)); // TODO
975975 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));
978978 const fiber = Fiber.allocate(event_loop) catch {
979979 start(context.ptr);
980980 return;
......@@ -985,7 +985,7 @@ fn asyncDetached(
985985 const closure: *DetachedClosure = @ptrFromInt(Fiber.max_context_align.max(.of(DetachedClosure)).backward(
986986 @intFromPtr(fiber.allocatedEnd()) - Fiber.max_context_size,
987987 ) - @sizeOf(DetachedClosure));
988 const stack_end: [*]align(16) usize = @alignCast(@ptrCast(closure));
988 const stack_end: [*]align(16) usize = @ptrCast(@alignCast(closure));
989989 (stack_end - 1)[0..1].* = .{@intFromPtr(&DetachedClosure.call)};
990990 fiber.* = .{
991991 .required_align = {},
......@@ -1035,8 +1035,8 @@ fn await(
10351035 result: []u8,
10361036 result_alignment: Alignment,
10371037) void {
1038 const event_loop: *EventLoop = @alignCast(@ptrCast(userdata));
1039 const future_fiber: *Fiber = @alignCast(@ptrCast(any_future));
1038 const event_loop: *EventLoop = @ptrCast(@alignCast(userdata));
1039 const future_fiber: *Fiber = @ptrCast(@alignCast(any_future));
10401040 if (@atomicLoad(?*Fiber, &future_fiber.awaiter, .acquire) != Fiber.finished)
10411041 event_loop.yield(null, .{ .register_awaiter = &future_fiber.awaiter });
10421042 @memcpy(result, future_fiber.resultBytes(result_alignment));
......@@ -1044,11 +1044,11 @@ fn await(
10441044}
10451045
10461046fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize {
1047 const el: *EventLoop = @alignCast(@ptrCast(userdata));
1047 const el: *EventLoop = @ptrCast(@alignCast(userdata));
10481048
10491049 // Optimization to avoid the yield below.
10501050 for (futures, 0..) |any_future, i| {
1051 const future_fiber: *Fiber = @alignCast(@ptrCast(any_future));
1051 const future_fiber: *Fiber = @ptrCast(@alignCast(any_future));
10521052 if (@atomicLoad(?*Fiber, &future_fiber.awaiter, .acquire) == Fiber.finished)
10531053 return i;
10541054 }
......@@ -1062,7 +1062,7 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize {
10621062 var result: ?usize = null;
10631063
10641064 for (futures, 0..) |any_future, i| {
1065 const future_fiber: *Fiber = @alignCast(@ptrCast(any_future));
1065 const future_fiber: *Fiber = @ptrCast(@alignCast(any_future));
10661066 if (@cmpxchgStrong(?*Fiber, &future_fiber.awaiter, my_fiber, null, .seq_cst, .seq_cst)) |awaiter| {
10671067 if (awaiter == Fiber.finished) {
10681068 if (result == null) result = i;
......@@ -1085,7 +1085,7 @@ fn cancel(
10851085 result: []u8,
10861086 result_alignment: Alignment,
10871087) void {
1088 const future_fiber: *Fiber = @alignCast(@ptrCast(any_future));
1088 const future_fiber: *Fiber = @ptrCast(@alignCast(any_future));
10891089 if (@atomicRmw(
10901090 ?*Thread,
10911091 &future_fiber.cancel_thread,
......@@ -1124,7 +1124,7 @@ fn createFile(
11241124 sub_path: []const u8,
11251125 flags: Io.File.CreateFlags,
11261126) Io.File.OpenError!Io.File {
1127 const el: *EventLoop = @alignCast(@ptrCast(userdata));
1127 const el: *EventLoop = @ptrCast(@alignCast(userdata));
11281128 const thread: *Thread = .current();
11291129 const iou = &thread.io_uring;
11301130 const fiber = thread.currentFiber();
......@@ -1220,13 +1220,13 @@ fn createFile(
12201220 }
12211221}
12221222
1223fn openFile(
1223fn fileOpen(
12241224 userdata: ?*anyopaque,
12251225 dir: Io.Dir,
12261226 sub_path: []const u8,
12271227 flags: Io.File.OpenFlags,
12281228) Io.File.OpenError!Io.File {
1229 const el: *EventLoop = @alignCast(@ptrCast(userdata));
1229 const el: *EventLoop = @ptrCast(@alignCast(userdata));
12301230 const thread: *Thread = .current();
12311231 const iou = &thread.io_uring;
12321232 const fiber = thread.currentFiber();
......@@ -1328,8 +1328,8 @@ fn openFile(
13281328 }
13291329}
13301330
1331fn closeFile(userdata: ?*anyopaque, file: Io.File) void {
1332 const el: *EventLoop = @alignCast(@ptrCast(userdata));
1331fn fileClose(userdata: ?*anyopaque, file: Io.File) void {
1332 const el: *EventLoop = @ptrCast(@alignCast(userdata));
13331333 const thread: *Thread = .current();
13341334 const iou = &thread.io_uring;
13351335 const fiber = thread.currentFiber();
......@@ -1365,7 +1365,7 @@ fn closeFile(userdata: ?*anyopaque, file: Io.File) void {
13651365}
13661366
13671367fn 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));
13691369 const thread: *Thread = .current();
13701370 const iou = &thread.io_uring;
13711371 const fiber = thread.currentFiber();
......@@ -1417,7 +1417,7 @@ fn pread(userdata: ?*anyopaque, file: Io.File, buffer: []u8, offset: std.posix.o
14171417}
14181418
14191419fn 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));
14211421 const thread: *Thread = .current();
14221422 const iou = &thread.io_uring;
14231423 const fiber = thread.currentFiber();
......@@ -1479,7 +1479,7 @@ fn now(userdata: ?*anyopaque, clockid: std.posix.clockid_t) Io.ClockGetTimeError
14791479}
14801480
14811481fn 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));
14831483 const thread: *Thread = .current();
14841484 const iou = &thread.io_uring;
14851485 const fiber = thread.currentFiber();
......@@ -1532,7 +1532,7 @@ fn sleep(userdata: ?*anyopaque, clockid: std.posix.clockid_t, deadline: Io.Deadl
15321532}
15331533
15341534fn 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));
15361536 el.yield(null, .{ .mutex_lock = .{ .prev_state = prev_state, .mutex = mutex } });
15371537}
15381538fn 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
15531553 .acquire,
15541554 ) orelse return) |next_state| maybe_waiting_fiber = @ptrFromInt(@intFromEnum(next_state));
15551555 maybe_waiting_fiber.?.queue_next = null;
1556 const el: *EventLoop = @alignCast(@ptrCast(userdata));
1556 const el: *EventLoop = @ptrCast(@alignCast(userdata));
15571557 el.yield(maybe_waiting_fiber.?, .reschedule);
15581558}
15591559
......@@ -1566,7 +1566,7 @@ const ConditionImpl = struct {
15661566};
15671567
15681568fn 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));
15701570 el.yield(null, .{ .condition_wait = .{ .cond = cond, .mutex = mutex } });
15711571 const thread = Thread.current();
15721572 const fiber = thread.currentFiber();
......@@ -1595,7 +1595,7 @@ fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) I
15951595}
15961596
15971597fn 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));
15991599 const waiting_fiber = @atomicRmw(?*Fiber, @as(*?*Fiber, @ptrCast(&cond.state)), .Xchg, null, .acquire) orelse return;
16001600 waiting_fiber.resultPointer(ConditionImpl).event = .{ .wake = wake };
16011601 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 @@
1const Pool = @This();
2
13const builtin = @import("builtin");
4const native_os = builtin.os.tag;
5const is_windows = native_os == .windows;
6const windows = std.os.windows;
7
28const std = @import("../std.zig");
39const Allocator = std.mem.Allocator;
410const assert = std.debug.assert;
511const WaitGroup = std.Thread.WaitGroup;
612const posix = std.posix;
713const Io = std.Io;
8const Pool = @This();
914
1015/// Thread-safe.
1116allocator: Allocator,
......@@ -23,6 +28,10 @@ threadlocal var current_closure: ?*AsyncClosure = null;
2328const max_iovecs_len = 8;
2429const splat_buffer_size = 64;
2530
31comptime {
32 assert(max_iovecs_len <= posix.IOV_MAX);
33}
34
2635pub const Runnable = struct {
2736 start: Start,
2837 node: std.SinglyLinkedList.Node = .{},
......@@ -104,10 +113,13 @@ pub fn io(pool: *Pool) Io {
104113 .conditionWake = conditionWake,
105114
106115 .createFile = createFile,
107 .openFile = openFile,
108 .closeFile = closeFile,
109 .pread = pread,
116 .fileOpen = fileOpen,
117 .fileClose = fileClose,
110118 .pwrite = pwrite,
119 .fileReadStreaming = fileReadStreaming,
120 .fileReadPositional = fileReadPositional,
121 .fileSeekBy = fileSeekBy,
122 .fileSeekTo = fileSeekTo,
111123
112124 .now = now,
113125 .sleep = sleep,
......@@ -631,7 +643,7 @@ fn createFile(
631643 return .{ .handle = fs_file.handle };
632644}
633645
634fn openFile(
646fn fileOpen(
635647 userdata: ?*anyopaque,
636648 dir: Io.Dir,
637649 sub_path: []const u8,
......@@ -644,21 +656,256 @@ fn openFile(
644656 return .{ .handle = fs_file.handle };
645657}
646658
647fn closeFile(userdata: ?*anyopaque, file: Io.File) void {
659fn fileClose(userdata: ?*anyopaque, file: Io.File) void {
648660 const pool: *Pool = @ptrCast(@alignCast(userdata));
649661 _ = pool;
650662 const fs_file: std.fs.File = .{ .handle = file.handle };
651663 return fs_file.close();
652664}
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 {
655667 const pool: *Pool = @ptrCast(@alignCast(userdata));
656 try pool.checkCancel();
657 const fs_file: std.fs.File = .{ .handle = file.handle };
658 return switch (offset) {
659 -1 => fs_file.read(buffer),
660 else => fs_file.pread(buffer, @bitCast(offset)),
668
669 if (is_windows) {
670 const DWORD = windows.DWORD;
671 var index: usize = 0;
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,
661770 };
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");
662909}
663910
664911fn 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();
55const std = @import("../std.zig");
66const assert = std.debug.assert;
77const Limit = std.Io.Limit;
8const File = std.fs.File;
8const File = std.Io.File;
99const testing = std.testing;
1010const Allocator = std.mem.Allocator;
1111const ArrayList = std.ArrayList;
......@@ -2778,7 +2778,8 @@ pub const Allocating = struct {
27782778 if (additional == 0) return error.EndOfStream;
27792779 a.ensureUnusedCapacity(limit.minInt64(additional)) catch return error.WriteFailed;
27802780 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;
27822783 a.writer.end += n;
27832784 return n;
27842785 }
lib/std/Io/net.zig+52-32
......@@ -17,9 +17,12 @@ pub const ListenOptions = struct {
1717 force_nonblocking: bool = false,
1818};
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 '.'.
2124pub const HostName = struct {
22 /// Externally managed memory. Already checked to be within `max_len`.
25 /// Externally managed memory. Already checked to be valid.
2326 bytes: []const u8,
2427
2528 pub const max_len = 255;
......@@ -55,13 +58,14 @@ pub const HostName = struct {
5558 family: ?IpAddress.Tag = null,
5659 };
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
6065 pub const LookupResult = struct {
6166 /// How many `LookupOptions.addresses_buffer` elements are populated.
62 addresses_len: usize,
63 /// Length zero means no canonical name returned.
64 canonical_name_len: usize,
67 addresses_len: usize = 0,
68 canonical_name: ?HostName = null,
6569 };
6670
6771 pub fn lookup(host_name: HostName, io: Io, options: LookupOptions) LookupError!LookupResult {
......@@ -75,17 +79,17 @@ pub const HostName = struct {
7579 if (options.family != .ip6) {
7680 if (IpAddress.parseIp4(name, options.port)) |addr| {
7781 options.addresses_buffer[0] = addr;
78 return .{ .addresses_len = 1, .canonical_name_len = 0 };
82 return .{ .addresses_len = 1 };
7983 } else |_| {}
8084 }
8185 if (options.family != .ip4) {
8286 if (IpAddress.parseIp6(name, options.port)) |addr| {
8387 options.addresses_buffer[0] = addr;
84 return .{ .addresses_len = 1, .canonical_name_len = 0 };
88 return .{ .addresses_len = 1 };
8589 } else |_| {}
8690 }
8791 {
88 const result = try lookupHosts(io, options);
92 const result = try lookupHosts(host_name, io, options);
8993 if (result.addresses_len > 0) return sortLookupResults(options, result);
9094 }
9195 {
......@@ -110,8 +114,12 @@ pub const HostName = struct {
110114 i += 1;
111115 }
112116 const canon_name = "localhost";
113 options.canonical_name_buffer[0..canon_name.len].* = canon_name.*;
114 return sortLookupResults(options, .{ .addresses_len = i, .canonical_name_len = canon_name.len });
117 const canon_name_dest = options.canonical_name_buffer[0..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 });
115123 }
116124 }
117125 {
......@@ -135,27 +143,27 @@ pub const HostName = struct {
135143 @panic("TODO");
136144 }
137145
138 fn lookupHosts(io: Io, options: LookupOptions) !LookupResult {
139 const file = Io.File.openFileAbsoluteZ(io, "/etc/hosts", .{}) catch |err| switch (err) {
146 fn lookupHosts(host_name: HostName, io: Io, options: LookupOptions) !LookupResult {
147 const file = Io.File.openAbsolute(io, "/etc/hosts", .{}) catch |err| switch (err) {
140148 error.FileNotFound,
141149 error.NotDir,
142150 error.AccessDenied,
143 => return,
151 => return .{},
152
144153 else => |e| return e,
145154 };
146 defer file.close();
155 defer file.close(io);
147156
148157 var line_buf: [512]u8 = undefined;
149158 var file_reader = file.reader(io, &line_buf);
150 return lookupHostsReader(options, &file_reader.interface) catch |err| switch (err) {
151 error.OutOfMemory => return error.OutOfMemory,
159 return lookupHostsReader(host_name, options, &file_reader.interface) catch |err| switch (err) {
152160 error.ReadFailed => return file_reader.err.?,
153161 };
154162 }
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 {
157165 var addresses_len: usize = 0;
158 var canonical_name_len: usize = 0;
166 var canonical_name: ?HostName = null;
159167 while (true) {
160168 const line = reader.takeDelimiterExclusive('\n') catch |err| switch (err) {
161169 error.StreamTooLong => {
......@@ -176,19 +184,20 @@ pub const HostName = struct {
176184 const ip_text = line_it.next() orelse continue;
177185 var first_name_text: ?[]const u8 = null;
178186 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)) {
180188 if (first_name_text == null) first_name_text = name_text;
181189 break;
182190 }
183191 } else continue;
184192
185 if (canonical_name_len == 0) {
186 if (HostName.init(first_name_text)) |name_text| {
187 if (name_text.len <= options.canonical_name_buffer.len) {
188 @memcpy(options.canonical_name_buffer[0..name_text.len], name_text);
189 canonical_name_len = name_text.len;
193 if (canonical_name == null) {
194 if (HostName.init(first_name_text.?)) |name_text| {
195 if (name_text.bytes.len <= options.canonical_name_buffer.len) {
196 const canonical_name_dest = options.canonical_name_buffer[0..name_text.bytes.len];
197 @memcpy(canonical_name_dest, name_text.bytes);
198 canonical_name = .{ .bytes = canonical_name_dest };
190199 }
191 }
200 } else |_| {}
192201 }
193202
194203 if (options.family != .ip6) {
......@@ -197,7 +206,7 @@ pub const HostName = struct {
197206 addresses_len += 1;
198207 if (options.addresses_buffer.len - addresses_len == 0) return .{
199208 .addresses_len = addresses_len,
200 .canonical_name_len = canonical_name_len,
209 .canonical_name = canonical_name,
201210 };
202211 } else |_| {}
203212 }
......@@ -207,11 +216,15 @@ pub const HostName = struct {
207216 addresses_len += 1;
208217 if (options.addresses_buffer.len - addresses_len == 0) return .{
209218 .addresses_len = addresses_len,
210 .canonical_name_len = canonical_name_len,
219 .canonical_name = canonical_name,
211220 };
212221 } else |_| {}
213222 }
214223 }
224 return .{
225 .addresses_len = addresses_len,
226 .canonical_name = canonical_name,
227 };
215228 }
216229
217230 pub const ConnectTcpError = LookupError || IpAddress.ConnectTcpError;
......@@ -289,9 +302,9 @@ pub const IpAddress = union(enum) {
289302 }
290303 }
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 {
293306 switch (a) {
294 .ip4, .ip6 => |x| return x.format(w),
307 inline .ip4, .ip6 => |x| return x.format(w),
295308 }
296309 }
297310
......@@ -365,7 +378,7 @@ pub const Ip4Address = struct {
365378 return error.Incomplete;
366379 }
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 {
369382 const bytes = &a.bytes;
370383 try w.print("{d}.{d}.{d}.{d}:{d}", .{ bytes[0], bytes[1], bytes[2], bytes[3], a.port });
371384 }
......@@ -393,6 +406,13 @@ pub const Ip6Address = struct {
393406 Incomplete,
394407 };
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
396416 pub fn parse(buffer: []const u8, port: u16) ParseError!Ip6Address {
397417 var result: Ip6Address = .{
398418 .port = port,
......@@ -504,7 +524,7 @@ pub const Ip6Address = struct {
504524 }
505525 }
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 {
508528 const bytes = &a.bytes;
509529 if (std.mem.eql(u8, bytes[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) {
510530 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;
1717/// The OS-specific file descriptor or file handle.
1818handle: Handle,
1919
20pub const Handle = posix.fd_t;
21pub const Mode = posix.mode_t;
22pub const INode = posix.ino_t;
20pub const Handle = std.Io.File.Handle;
21pub const Mode = std.Io.File.Mode;
22pub const INode = std.Io.File.INode;
2323pub const Uid = posix.uid_t;
2424pub const Gid = posix.gid_t;
25
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};
25pub const Kind = std.Io.File.Kind;
3926
4027/// This is the default mode given to POSIX operating systems for creating
4128/// files. `0o666` is "-rw-rw-rw-" which is counter-intuitive at first,
......@@ -399,115 +386,11 @@ pub fn mode(self: File) ModeError!Mode {
399386 return (try self.stat()).mode;
400387}
401388
402pub const Stat = struct {
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};
389pub const Stat = std.Io.File.Stat;
506390
507391pub const StatError = posix.FStatError;
508392
509393/// Returns `Stat` containing basic information about the `File`.
510/// TODO: integrate with async I/O
511394pub fn stat(self: File) StatError!Stat {
512395 if (builtin.os.tag == .windows) {
513396 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
......@@ -1727,7 +1610,7 @@ pub const Writer = struct {
17271610
17281611 pub fn sendFile(
17291612 io_w: *std.Io.Writer,
1730 file_reader: *Reader,
1613 file_reader: *std.Io.File.Reader,
17311614 limit: std.Io.Limit,
17321615 ) std.Io.Writer.FileError!usize {
17331616 const reader_buffered = file_reader.interface.buffered();
......@@ -1994,7 +1877,7 @@ pub const Writer = struct {
19941877
19951878 fn sendFileBuffered(
19961879 io_w: *std.Io.Writer,
1997 file_reader: *Reader,
1880 file_reader: *std.Io.File.Reader,
19981881 reader_buffered: []const u8,
19991882 ) std.Io.Writer.FileError!usize {
20001883 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 {
806806 system.exit(status);
807807}
808808
809pub const ReadError = error{
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;
809pub const ReadError = std.Io.File.ReadStreamingError;
839810
840811/// Returns the number of bytes that were read, which can be less than
841812/// buf.len. If 0 bytes were read, that means EOF.
......@@ -922,7 +893,6 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
922893/// a pointer within the address space of the application.
923894pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
924895 if (native_os == .windows) {
925 // TODO improve this to use ReadFileScatter
926896 if (iov.len == 0) return 0;
927897 const first = iov[0];
928898 return read(fd, first.base[0..first.len]);
......@@ -970,7 +940,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
970940 }
971941}
972942
973pub const PReadError = ReadError || error{Unseekable};
943pub const PReadError = std.Io.ReadPositionalError;
974944
975945/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
976946///
......@@ -5376,13 +5346,7 @@ pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void {
53765346 }
53775347}
53785348
5379pub const SeekError = error{
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;
5349pub const SeekError = std.Io.File.SeekError;
53865350
53875351/// Repositions read/write file offset relative to the beginning.
53885352pub 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 {
75587522 }
75597523}
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
75637527/// Whether or not `error.Unexpected` will print its value and a stack trace.
75647528///
......@@ -7570,17 +7534,7 @@ pub const unexpected_error_tracing = builtin.mode == .Debug and switch (builtin.
75707534 else => false,
75717535};
75727536
7573pub const UnexpectedError = error{
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};
7537pub const UnexpectedError = std.Io.UnexpectedError;
75847538
75857539/// Call this when you made a syscall or something that sets errno
75867540/// and you get an unexpected error.