authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-02-06 17:56:40-05:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-02-06 18:05:50-05:00
log0b5bcd2f56a84e66d5c700744ec1838381893667
tree5b640a57055e50636fe7a9f782915b2126395ef3
parent704cd977bdcdfa8cff4e70aaad93857d9b622fc7
signaturelock-open Commit is signed but in an unrecognized format.

more std lib async I/O integration

* `zig test` gainst `--test-evented-io` parameter and gains the ability to seamlessly run async tests. * `std.ChildProcess` opens its child process pipe with O_NONBLOCK when using evented I/O * `std.io.getStdErr()` gives a File that is blocking even in evented I/O mode. * Delete `std.event.fs`. The functionality is now merged into `std.fs` and async file system access (using a dedicated thread) is automatically handled. * `std.fs.File` can be configured to specify whether its handle is expected to block, and whether that is OK to block even when in async I/O mode. This makes async I/O work correctly for e.g. the file system as well as network. * `std.fs.File` has some deprecated functions removed. * Missing readv,writev,pread,pwrite,preadv,pwritev functions are added to `std.os` and `std.fs.File`. They are all integrated with async I/O. * `std.fs.Watch` is still bit rotted and needs to be audited in light of the new async/await syntax. * `std.io.OutStream` integrates with async I/O * linked list nodes in the std lib have default `null` values for `prev` and `next`. * Windows async I/O integration is enabled for reading/writing file handles. * Added `std.os.mode_t`. Integer sizes need to be audited. * Fixed #4403 which was causing compiler to crash. This is working towards: ./zig test ../test/stage1/behavior.zig --test-evented-io Which does not successfully build yet. I'd like to enable behavioral tests and std lib tests with --test-evented-io in the test matrix in the future, to prevent regressions.

24 files changed, 1572 insertions(+), 1691 deletions(-)

lib/std/builtin.zig+1
...@@ -458,6 +458,7 @@ pub const ExportOptions = struct {...@@ -458,6 +458,7 @@ pub const ExportOptions = struct {
458pub const TestFn = struct {458pub const TestFn = struct {
459 name: []const u8,459 name: []const u8,
460 func: fn () anyerror!void,460 func: fn () anyerror!void,
461 async_frame_size: ?usize,
461};462};
462463
463/// This function type is used by the Zig language code generation and464/// This function type is used by the Zig language code generation and
lib/std/child_process.zig+44-15
...@@ -329,17 +329,18 @@ pub const ChildProcess = struct {...@@ -329,17 +329,18 @@ pub const ChildProcess = struct {
329 }329 }
330330
331 fn spawnPosix(self: *ChildProcess) SpawnError!void {331 fn spawnPosix(self: *ChildProcess) SpawnError!void {
332 const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try os.pipe() else undefined;332 const pipe_flags = if (io.is_async) os.O_NONBLOCK else 0;
333 const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined;
333 errdefer if (self.stdin_behavior == StdIo.Pipe) {334 errdefer if (self.stdin_behavior == StdIo.Pipe) {
334 destroyPipe(stdin_pipe);335 destroyPipe(stdin_pipe);
335 };336 };
336337
337 const stdout_pipe = if (self.stdout_behavior == StdIo.Pipe) try os.pipe() else undefined;338 const stdout_pipe = if (self.stdout_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined;
338 errdefer if (self.stdout_behavior == StdIo.Pipe) {339 errdefer if (self.stdout_behavior == StdIo.Pipe) {
339 destroyPipe(stdout_pipe);340 destroyPipe(stdout_pipe);
340 };341 };
341342
342 const stderr_pipe = if (self.stderr_behavior == StdIo.Pipe) try os.pipe() else undefined;343 const stderr_pipe = if (self.stderr_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined;
343 errdefer if (self.stderr_behavior == StdIo.Pipe) {344 errdefer if (self.stderr_behavior == StdIo.Pipe) {
344 destroyPipe(stderr_pipe);345 destroyPipe(stderr_pipe);
345 };346 };
...@@ -426,17 +427,26 @@ pub const ChildProcess = struct {...@@ -426,17 +427,26 @@ pub const ChildProcess = struct {
426 // we are the parent427 // we are the parent
427 const pid = @intCast(i32, pid_result);428 const pid = @intCast(i32, pid_result);
428 if (self.stdin_behavior == StdIo.Pipe) {429 if (self.stdin_behavior == StdIo.Pipe) {
429 self.stdin = File.openHandle(stdin_pipe[1]);430 self.stdin = File{
431 .handle = stdin_pipe[1],
432 .io_mode = std.io.mode,
433 };
430 } else {434 } else {
431 self.stdin = null;435 self.stdin = null;
432 }436 }
433 if (self.stdout_behavior == StdIo.Pipe) {437 if (self.stdout_behavior == StdIo.Pipe) {
434 self.stdout = File.openHandle(stdout_pipe[0]);438 self.stdout = File{
439 .handle = stdout_pipe[0],
440 .io_mode = std.io.mode,
441 };
435 } else {442 } else {
436 self.stdout = null;443 self.stdout = null;
437 }444 }
438 if (self.stderr_behavior == StdIo.Pipe) {445 if (self.stderr_behavior == StdIo.Pipe) {
439 self.stderr = File.openHandle(stderr_pipe[0]);446 self.stderr = File{
447 .handle = stderr_pipe[0],
448 .io_mode = std.io.mode,
449 };
440 } else {450 } else {
441 self.stderr = null;451 self.stderr = null;
442 }452 }
...@@ -661,17 +671,26 @@ pub const ChildProcess = struct {...@@ -661,17 +671,26 @@ pub const ChildProcess = struct {
661 };671 };
662672
663 if (g_hChildStd_IN_Wr) |h| {673 if (g_hChildStd_IN_Wr) |h| {
664 self.stdin = File.openHandle(h);674 self.stdin = File{
675 .handle = h,
676 .io_mode = io.mode,
677 };
665 } else {678 } else {
666 self.stdin = null;679 self.stdin = null;
667 }680 }
668 if (g_hChildStd_OUT_Rd) |h| {681 if (g_hChildStd_OUT_Rd) |h| {
669 self.stdout = File.openHandle(h);682 self.stdout = File{
683 .handle = h,
684 .io_mode = io.mode,
685 };
670 } else {686 } else {
671 self.stdout = null;687 self.stdout = null;
672 }688 }
673 if (g_hChildStd_ERR_Rd) |h| {689 if (g_hChildStd_ERR_Rd) |h| {
674 self.stderr = File.openHandle(h);690 self.stderr = File{
691 .handle = h,
692 .io_mode = io.mode,
693 };
675 } else {694 } else {
676 self.stderr = null;695 self.stderr = null;
677 }696 }
...@@ -693,10 +712,10 @@ pub const ChildProcess = struct {...@@ -693,10 +712,10 @@ pub const ChildProcess = struct {
693712
694 fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void {713 fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void {
695 switch (stdio) {714 switch (stdio) {
696 StdIo.Pipe => try os.dup2(pipe_fd, std_fileno),715 .Pipe => try os.dup2(pipe_fd, std_fileno),
697 StdIo.Close => os.close(std_fileno),716 .Close => os.close(std_fileno),
698 StdIo.Inherit => {},717 .Inherit => {},
699 StdIo.Ignore => try os.dup2(dev_null_fd, std_fileno),718 .Ignore => try os.dup2(dev_null_fd, std_fileno),
700 }719 }
701 }720 }
702};721};
...@@ -811,12 +830,22 @@ fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn {...@@ -811,12 +830,22 @@ fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn {
811const ErrInt = @IntType(false, @sizeOf(anyerror) * 8);830const ErrInt = @IntType(false, @sizeOf(anyerror) * 8);
812831
813fn writeIntFd(fd: i32, value: ErrInt) !void {832fn writeIntFd(fd: i32, value: ErrInt) !void {
814 const stream = &File.openHandle(fd).outStream().stream;833 const file = File{
834 .handle = fd,
835 .io_mode = .blocking,
836 .async_block_allowed = File.async_block_allowed_yes,
837 };
838 const stream = &file.outStream().stream;
815 stream.writeIntNative(u64, @intCast(u64, value)) catch return error.SystemResources;839 stream.writeIntNative(u64, @intCast(u64, value)) catch return error.SystemResources;
816}840}
817841
818fn readIntFd(fd: i32) !ErrInt {842fn readIntFd(fd: i32) !ErrInt {
819 const stream = &File.openHandle(fd).inStream().stream;843 const file = File{
844 .handle = fd,
845 .io_mode = .blocking,
846 .async_block_allowed = File.async_block_allowed_yes,
847 };
848 const stream = &file.inStream().stream;
820 return @intCast(ErrInt, stream.readIntNative(u64) catch return error.SystemResources);849 return @intCast(ErrInt, stream.readIntNative(u64) catch return error.SystemResources);
821}850}
822851
lib/std/debug.zig+24-24
...@@ -50,7 +50,7 @@ pub fn warn(comptime fmt: []const u8, args: var) void {...@@ -50,7 +50,7 @@ pub fn warn(comptime fmt: []const u8, args: var) void {
50 const held = stderr_mutex.acquire();50 const held = stderr_mutex.acquire();
51 defer held.release();51 defer held.release();
52 const stderr = getStderrStream();52 const stderr = getStderrStream();
53 stderr.print(fmt, args) catch return;53 noasync stderr.print(fmt, args) catch return;
54}54}
5555
56pub fn getStderrStream() *io.OutStream(File.WriteError) {56pub fn getStderrStream() *io.OutStream(File.WriteError) {
...@@ -102,15 +102,15 @@ pub fn detectTTYConfig() TTY.Config {...@@ -102,15 +102,15 @@ pub fn detectTTYConfig() TTY.Config {
102pub fn dumpCurrentStackTrace(start_addr: ?usize) void {102pub fn dumpCurrentStackTrace(start_addr: ?usize) void {
103 const stderr = getStderrStream();103 const stderr = getStderrStream();
104 if (builtin.strip_debug_info) {104 if (builtin.strip_debug_info) {
105 stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;105 noasync stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
106 return;106 return;
107 }107 }
108 const debug_info = getSelfDebugInfo() catch |err| {108 const debug_info = getSelfDebugInfo() catch |err| {
109 stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return;109 noasync stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return;
110 return;110 return;
111 };111 };
112 writeCurrentStackTrace(stderr, debug_info, detectTTYConfig(), start_addr) catch |err| {112 writeCurrentStackTrace(stderr, debug_info, detectTTYConfig(), start_addr) catch |err| {
113 stderr.print("Unable to dump stack trace: {}\n", .{@errorName(err)}) catch return;113 noasync stderr.print("Unable to dump stack trace: {}\n", .{@errorName(err)}) catch return;
114 return;114 return;
115 };115 };
116}116}
...@@ -121,11 +121,11 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void {...@@ -121,11 +121,11 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void {
121pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void {121pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void {
122 const stderr = getStderrStream();122 const stderr = getStderrStream();
123 if (builtin.strip_debug_info) {123 if (builtin.strip_debug_info) {
124 stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;124 noasync stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
125 return;125 return;
126 }126 }
127 const debug_info = getSelfDebugInfo() catch |err| {127 const debug_info = getSelfDebugInfo() catch |err| {
128 stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return;128 noasync stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return;
129 return;129 return;
130 };130 };
131 const tty_config = detectTTYConfig();131 const tty_config = detectTTYConfig();
...@@ -195,15 +195,15 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *builtin.StackTrace...@@ -195,15 +195,15 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *builtin.StackTrace
195pub fn dumpStackTrace(stack_trace: builtin.StackTrace) void {195pub fn dumpStackTrace(stack_trace: builtin.StackTrace) void {
196 const stderr = getStderrStream();196 const stderr = getStderrStream();
197 if (builtin.strip_debug_info) {197 if (builtin.strip_debug_info) {
198 stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;198 noasync stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
199 return;199 return;
200 }200 }
201 const debug_info = getSelfDebugInfo() catch |err| {201 const debug_info = getSelfDebugInfo() catch |err| {
202 stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return;202 noasync stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return;
203 return;203 return;
204 };204 };
205 writeStackTrace(stack_trace, stderr, getDebugInfoAllocator(), debug_info, detectTTYConfig()) catch |err| {205 writeStackTrace(stack_trace, stderr, getDebugInfoAllocator(), debug_info, detectTTYConfig()) catch |err| {
206 stderr.print("Unable to dump stack trace: {}\n", .{@errorName(err)}) catch return;206 noasync stderr.print("Unable to dump stack trace: {}\n", .{@errorName(err)}) catch return;
207 return;207 return;
208 };208 };
209}209}
...@@ -244,7 +244,7 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c...@@ -244,7 +244,7 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c
244 switch (@atomicRmw(u8, &panicking, .Add, 1, .SeqCst)) {244 switch (@atomicRmw(u8, &panicking, .Add, 1, .SeqCst)) {
245 0 => {245 0 => {
246 const stderr = getStderrStream();246 const stderr = getStderrStream();
247 stderr.print(format ++ "\n", args) catch os.abort();247 noasync stderr.print(format ++ "\n", args) catch os.abort();
248 if (trace) |t| {248 if (trace) |t| {
249 dumpStackTrace(t.*);249 dumpStackTrace(t.*);
250 }250 }
...@@ -556,12 +556,12 @@ pub const TTY = struct {...@@ -556,12 +556,12 @@ pub const TTY = struct {
556 switch (conf) {556 switch (conf) {
557 .no_color => return,557 .no_color => return,
558 .escape_codes => switch (color) {558 .escape_codes => switch (color) {
559 .Red => out_stream.write(RED) catch return,559 .Red => noasync out_stream.write(RED) catch return,
560 .Green => out_stream.write(GREEN) catch return,560 .Green => noasync out_stream.write(GREEN) catch return,
561 .Cyan => out_stream.write(CYAN) catch return,561 .Cyan => noasync out_stream.write(CYAN) catch return,
562 .White, .Bold => out_stream.write(WHITE) catch return,562 .White, .Bold => noasync out_stream.write(WHITE) catch return,
563 .Dim => out_stream.write(DIM) catch return,563 .Dim => noasync out_stream.write(DIM) catch return,
564 .Reset => out_stream.write(RESET) catch return,564 .Reset => noasync out_stream.write(RESET) catch return,
565 },565 },
566 .windows_api => if (builtin.os == .windows) {566 .windows_api => if (builtin.os == .windows) {
567 const S = struct {567 const S = struct {
...@@ -717,17 +717,17 @@ fn printLineInfo(...@@ -717,17 +717,17 @@ fn printLineInfo(
717 tty_config.setColor(out_stream, .White);717 tty_config.setColor(out_stream, .White);
718718
719 if (line_info) |*li| {719 if (line_info) |*li| {
720 try out_stream.print("{}:{}:{}", .{ li.file_name, li.line, li.column });720 try noasync out_stream.print("{}:{}:{}", .{ li.file_name, li.line, li.column });
721 } else {721 } else {
722 try out_stream.print("???:?:?", .{});722 try noasync out_stream.write("???:?:?");
723 }723 }
724724
725 tty_config.setColor(out_stream, .Reset);725 tty_config.setColor(out_stream, .Reset);
726 try out_stream.write(": ");726 try noasync out_stream.write(": ");
727 tty_config.setColor(out_stream, .Dim);727 tty_config.setColor(out_stream, .Dim);
728 try out_stream.print("0x{x} in {} ({})", .{ address, symbol_name, compile_unit_name });728 try noasync out_stream.print("0x{x} in {} ({})", .{ address, symbol_name, compile_unit_name });
729 tty_config.setColor(out_stream, .Reset);729 tty_config.setColor(out_stream, .Reset);
730 try out_stream.write("\n");730 try noasync out_stream.write("\n");
731731
732 // Show the matching source code line if possible732 // Show the matching source code line if possible
733 if (line_info) |li| {733 if (line_info) |li| {
...@@ -736,12 +736,12 @@ fn printLineInfo(...@@ -736,12 +736,12 @@ fn printLineInfo(
736 // The caret already takes one char736 // The caret already takes one char
737 const space_needed = @intCast(usize, li.column - 1);737 const space_needed = @intCast(usize, li.column - 1);
738738
739 try out_stream.writeByteNTimes(' ', space_needed);739 try noasync out_stream.writeByteNTimes(' ', space_needed);
740 tty_config.setColor(out_stream, .Green);740 tty_config.setColor(out_stream, .Green);
741 try out_stream.write("^");741 try noasync out_stream.write("^");
742 tty_config.setColor(out_stream, .Reset);742 tty_config.setColor(out_stream, .Reset);
743 }743 }
744 try out_stream.write("\n");744 try noasync out_stream.write("\n");
745 } else |err| switch (err) {745 } else |err| switch (err) {
746 error.EndOfFile, error.FileNotFound => {},746 error.EndOfFile, error.FileNotFound => {},
747 error.BadPathName => {},747 error.BadPathName => {},
lib/std/event.zig-2
...@@ -6,11 +6,9 @@ pub const Locked = @import("event/locked.zig").Locked;...@@ -6,11 +6,9 @@ pub const Locked = @import("event/locked.zig").Locked;
6pub const RwLock = @import("event/rwlock.zig").RwLock;6pub const RwLock = @import("event/rwlock.zig").RwLock;
7pub const RwLocked = @import("event/rwlocked.zig").RwLocked;7pub const RwLocked = @import("event/rwlocked.zig").RwLocked;
8pub const Loop = @import("event/loop.zig").Loop;8pub const Loop = @import("event/loop.zig").Loop;
9pub const fs = @import("event/fs.zig");
109
11test "import event tests" {10test "import event tests" {
12 _ = @import("event/channel.zig");11 _ = @import("event/channel.zig");
13 _ = @import("event/fs.zig");
14 _ = @import("event/future.zig");12 _ = @import("event/future.zig");
15 _ = @import("event/group.zig");13 _ = @import("event/group.zig");
16 _ = @import("event/lock.zig");14 _ = @import("event/lock.zig");
lib/std/event/fs.zig deleted-1418
...@@ -1,1418 +0,0 @@
1const builtin = @import("builtin");
2const std = @import("../std.zig");
3const event = std.event;
4const assert = std.debug.assert;
5const testing = std.testing;
6const os = std.os;
7const mem = std.mem;
8const windows = os.windows;
9const Loop = event.Loop;
10const fd_t = os.fd_t;
11const File = std.fs.File;
12const Allocator = mem.Allocator;
13
14//! TODO mege this with `std.fs`
15
16const global_event_loop = Loop.instance orelse
17 @compileError("std.event.fs currently only works with event-based I/O");
18
19pub const RequestNode = std.atomic.Queue(Request).Node;
20
21pub const Request = struct {
22 msg: Msg,
23 finish: Finish,
24
25 pub const Finish = union(enum) {
26 TickNode: Loop.NextTickNode,
27 DeallocCloseOperation: *CloseOperation,
28 NoAction,
29 };
30
31 pub const Msg = union(enum) {
32 WriteV: WriteV,
33 PWriteV: PWriteV,
34 PReadV: PReadV,
35 Open: Open,
36 Close: Close,
37 WriteFile: WriteFile,
38 End, // special - means the fs thread should exit
39
40 pub const WriteV = struct {
41 fd: fd_t,
42 iov: []const os.iovec_const,
43 result: Error!void,
44
45 pub const Error = os.WriteError;
46 };
47
48 pub const PWriteV = struct {
49 fd: fd_t,
50 iov: []const os.iovec_const,
51 offset: usize,
52 result: Error!void,
53
54 pub const Error = os.WriteError;
55 };
56
57 pub const PReadV = struct {
58 fd: fd_t,
59 iov: []const os.iovec,
60 offset: usize,
61 result: Error!usize,
62
63 pub const Error = os.ReadError;
64 };
65
66 pub const Open = struct {
67 path: [:0]const u8,
68 flags: u32,
69 mode: File.Mode,
70 result: Error!fd_t,
71
72 pub const Error = File.OpenError;
73 };
74
75 pub const WriteFile = struct {
76 path: [:0]const u8,
77 contents: []const u8,
78 mode: File.Mode,
79 result: Error!void,
80
81 pub const Error = File.OpenError || File.WriteError;
82 };
83
84 pub const Close = struct {
85 fd: fd_t,
86 };
87 };
88};
89
90pub const PWriteVError = error{OutOfMemory} || File.WriteError;
91
92/// data - just the inner references - must live until pwritev frame completes.
93pub fn pwritev(allocator: *Allocator, fd: fd_t, data: []const []const u8, offset: usize) PWriteVError!void {
94 switch (builtin.os) {
95 .macosx,
96 .linux,
97 .freebsd,
98 .netbsd,
99 .dragonfly,
100 => {
101 const iovecs = try allocator.alloc(os.iovec_const, data.len);
102 defer allocator.free(iovecs);
103
104 for (data) |buf, i| {
105 iovecs[i] = os.iovec_const{
106 .iov_base = buf.ptr,
107 .iov_len = buf.len,
108 };
109 }
110
111 return pwritevPosix(fd, iovecs, offset);
112 },
113 .windows => {
114 const data_copy = try std.mem.dupe(allocator, []const u8, data);
115 defer allocator.free(data_copy);
116 return pwritevWindows(fd, data, offset);
117 },
118 else => @compileError("Unsupported OS"),
119 }
120}
121
122/// data must outlive the returned frame
123pub fn pwritevWindows(fd: fd_t, data: []const []const u8, offset: usize) os.WindowsWriteError!void {
124 if (data.len == 0) return;
125 if (data.len == 1) return pwriteWindows(fd, data[0], offset);
126
127 // TODO do these in parallel
128 var off = offset;
129 for (data) |buf| {
130 try pwriteWindows(fd, buf, off);
131 off += buf.len;
132 }
133}
134
135pub fn pwriteWindows(fd: fd_t, data: []const u8, offset: u64) os.WindowsWriteError!void {
136 var resume_node = Loop.ResumeNode.Basic{
137 .base = Loop.ResumeNode{
138 .id = Loop.ResumeNode.Id.Basic,
139 .handle = @frame(),
140 .overlapped = windows.OVERLAPPED{
141 .Internal = 0,
142 .InternalHigh = 0,
143 .Offset = @truncate(u32, offset),
144 .OffsetHigh = @truncate(u32, offset >> 32),
145 .hEvent = null,
146 },
147 },
148 };
149 // TODO only call create io completion port once per fd
150 _ = windows.CreateIoCompletionPort(fd, global_event_loop.os_data.io_port, undefined, undefined);
151 global_event_loop.beginOneEvent();
152 errdefer global_event_loop.finishOneEvent();
153
154 errdefer {
155 _ = windows.kernel32.CancelIoEx(fd, &resume_node.base.overlapped);
156 }
157 suspend {
158 _ = windows.kernel32.WriteFile(fd, data.ptr, @intCast(windows.DWORD, data.len), null, &resume_node.base.overlapped);
159 }
160 var bytes_transferred: windows.DWORD = undefined;
161 if (windows.kernel32.GetOverlappedResult(fd, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) {
162 switch (windows.kernel32.GetLastError()) {
163 .IO_PENDING => unreachable,
164 .INVALID_USER_BUFFER => return error.SystemResources,
165 .NOT_ENOUGH_MEMORY => return error.SystemResources,
166 .OPERATION_ABORTED => return error.OperationAborted,
167 .NOT_ENOUGH_QUOTA => return error.SystemResources,
168 .BROKEN_PIPE => return error.BrokenPipe,
169 else => |err| return windows.unexpectedError(err),
170 }
171 }
172}
173
174/// iovecs must live until pwritev frame completes.
175pub fn pwritevPosix(fd: fd_t, iovecs: []const os.iovec_const, offset: usize) os.WriteError!void {
176 var req_node = RequestNode{
177 .prev = null,
178 .next = null,
179 .data = Request{
180 .msg = Request.Msg{
181 .PWriteV = Request.Msg.PWriteV{
182 .fd = fd,
183 .iov = iovecs,
184 .offset = offset,
185 .result = undefined,
186 },
187 },
188 .finish = Request.Finish{
189 .TickNode = Loop.NextTickNode{
190 .prev = null,
191 .next = null,
192 .data = @frame(),
193 },
194 },
195 },
196 };
197
198 errdefer global_event_loop.posixFsCancel(&req_node);
199
200 suspend {
201 global_event_loop.posixFsRequest(&req_node);
202 }
203
204 return req_node.data.msg.PWriteV.result;
205}
206
207/// iovecs must live until pwritev frame completes.
208pub fn writevPosix(fd: fd_t, iovecs: []const os.iovec_const) os.WriteError!void {
209 var req_node = RequestNode{
210 .prev = null,
211 .next = null,
212 .data = Request{
213 .msg = Request.Msg{
214 .WriteV = Request.Msg.WriteV{
215 .fd = fd,
216 .iov = iovecs,
217 .result = undefined,
218 },
219 },
220 .finish = Request.Finish{
221 .TickNode = Loop.NextTickNode{
222 .prev = null,
223 .next = null,
224 .data = @frame(),
225 },
226 },
227 },
228 };
229
230 suspend {
231 global_event_loop.posixFsRequest(&req_node);
232 }
233
234 return req_node.data.msg.WriteV.result;
235}
236
237pub const PReadVError = error{OutOfMemory} || File.ReadError;
238
239/// data - just the inner references - must live until preadv frame completes.
240pub fn preadv(allocator: *Allocator, fd: fd_t, data: []const []u8, offset: usize) PReadVError!usize {
241 assert(data.len != 0);
242 switch (builtin.os) {
243 .macosx,
244 .linux,
245 .freebsd,
246 .netbsd,
247 .dragonfly,
248 => {
249 const iovecs = try allocator.alloc(os.iovec, data.len);
250 defer allocator.free(iovecs);
251
252 for (data) |buf, i| {
253 iovecs[i] = os.iovec{
254 .iov_base = buf.ptr,
255 .iov_len = buf.len,
256 };
257 }
258
259 return preadvPosix(fd, iovecs, offset);
260 },
261 .windows => {
262 const data_copy = try std.mem.dupe(allocator, []u8, data);
263 defer allocator.free(data_copy);
264 return preadvWindows(fd, data_copy, offset);
265 },
266 else => @compileError("Unsupported OS"),
267 }
268}
269
270/// data must outlive the returned frame
271pub fn preadvWindows(fd: fd_t, data: []const []u8, offset: u64) !usize {
272 assert(data.len != 0);
273 if (data.len == 1) return preadWindows(fd, data[0], offset);
274
275 // TODO do these in parallel?
276 var off: usize = 0;
277 var iov_i: usize = 0;
278 var inner_off: usize = 0;
279 while (true) {
280 const v = data[iov_i];
281 const amt_read = try preadWindows(fd, v[inner_off .. v.len - inner_off], offset + off);
282 off += amt_read;
283 inner_off += amt_read;
284 if (inner_off == v.len) {
285 iov_i += 1;
286 inner_off = 0;
287 if (iov_i == data.len) {
288 return off;
289 }
290 }
291 if (amt_read == 0) return off; // EOF
292 }
293}
294
295pub fn preadWindows(fd: fd_t, data: []u8, offset: u64) !usize {
296 var resume_node = Loop.ResumeNode.Basic{
297 .base = Loop.ResumeNode{
298 .id = Loop.ResumeNode.Id.Basic,
299 .handle = @frame(),
300 .overlapped = windows.OVERLAPPED{
301 .Internal = 0,
302 .InternalHigh = 0,
303 .Offset = @truncate(u32, offset),
304 .OffsetHigh = @truncate(u32, offset >> 32),
305 .hEvent = null,
306 },
307 },
308 };
309 // TODO only call create io completion port once per fd
310 _ = windows.CreateIoCompletionPort(fd, global_event_loop.os_data.io_port, undefined, undefined) catch undefined;
311 global_event_loop.beginOneEvent();
312 errdefer global_event_loop.finishOneEvent();
313
314 errdefer {
315 _ = windows.kernel32.CancelIoEx(fd, &resume_node.base.overlapped);
316 }
317 suspend {
318 _ = windows.kernel32.ReadFile(fd, data.ptr, @intCast(windows.DWORD, data.len), null, &resume_node.base.overlapped);
319 }
320 var bytes_transferred: windows.DWORD = undefined;
321 if (windows.kernel32.GetOverlappedResult(fd, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) {
322 switch (windows.kernel32.GetLastError()) {
323 .IO_PENDING => unreachable,
324 .OPERATION_ABORTED => return error.OperationAborted,
325 .BROKEN_PIPE => return error.BrokenPipe,
326 .HANDLE_EOF => return @as(usize, bytes_transferred),
327 else => |err| return windows.unexpectedError(err),
328 }
329 }
330 return @as(usize, bytes_transferred);
331}
332
333/// iovecs must live until preadv frame completes
334pub fn preadvPosix(fd: fd_t, iovecs: []const os.iovec, offset: usize) os.ReadError!usize {
335 var req_node = RequestNode{
336 .prev = null,
337 .next = null,
338 .data = Request{
339 .msg = Request.Msg{
340 .PReadV = Request.Msg.PReadV{
341 .fd = fd,
342 .iov = iovecs,
343 .offset = offset,
344 .result = undefined,
345 },
346 },
347 .finish = Request.Finish{
348 .TickNode = Loop.NextTickNode{
349 .prev = null,
350 .next = null,
351 .data = @frame(),
352 },
353 },
354 },
355 };
356
357 errdefer global_event_loop.posixFsCancel(&req_node);
358
359 suspend {
360 global_event_loop.posixFsRequest(&req_node);
361 }
362
363 return req_node.data.msg.PReadV.result;
364}
365
366pub fn openPosix(path: []const u8, flags: u32, mode: File.Mode) File.OpenError!fd_t {
367 const path_c = try std.os.toPosixPath(path);
368
369 var req_node = RequestNode{
370 .prev = null,
371 .next = null,
372 .data = Request{
373 .msg = Request.Msg{
374 .Open = Request.Msg.Open{
375 .path = path_c[0..path.len],
376 .flags = flags,
377 .mode = mode,
378 .result = undefined,
379 },
380 },
381 .finish = Request.Finish{
382 .TickNode = Loop.NextTickNode{
383 .prev = null,
384 .next = null,
385 .data = @frame(),
386 },
387 },
388 },
389 };
390
391 errdefer global_event_loop.posixFsCancel(&req_node);
392
393 suspend {
394 global_event_loop.posixFsRequest(&req_node);
395 }
396
397 return req_node.data.msg.Open.result;
398}
399
400pub fn openRead(path: []const u8) File.OpenError!fd_t {
401 switch (builtin.os) {
402 .macosx, .linux, .freebsd, .netbsd, .dragonfly => {
403 const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0;
404 const flags = O_LARGEFILE | os.O_RDONLY | os.O_CLOEXEC;
405 return openPosix(path, flags, File.default_mode);
406 },
407
408 .windows => return windows.CreateFile(
409 path,
410 windows.GENERIC_READ,
411 windows.FILE_SHARE_READ,
412 null,
413 windows.OPEN_EXISTING,
414 windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED,
415 null,
416 ),
417
418 else => @compileError("Unsupported OS"),
419 }
420}
421
422/// Creates if does not exist. Truncates the file if it exists.
423/// Uses the default mode.
424pub fn openWrite(path: []const u8) File.OpenError!fd_t {
425 return openWriteMode(path, File.default_mode);
426}
427
428/// Creates if does not exist. Truncates the file if it exists.
429pub fn openWriteMode(path: []const u8, mode: File.Mode) File.OpenError!fd_t {
430 switch (builtin.os) {
431 .macosx,
432 .linux,
433 .freebsd,
434 .netbsd,
435 .dragonfly,
436 => {
437 const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0;
438 const flags = O_LARGEFILE | os.O_WRONLY | os.O_CREAT | os.O_CLOEXEC | os.O_TRUNC;
439 return openPosix(path, flags, File.default_mode);
440 },
441 .windows => return windows.CreateFile(
442 path,
443 windows.GENERIC_WRITE,
444 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
445 null,
446 windows.CREATE_ALWAYS,
447 windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED,
448 null,
449 ),
450 else => @compileError("Unsupported OS"),
451 }
452}
453
454/// Creates if does not exist. Does not truncate.
455pub fn openReadWrite(path: []const u8, mode: File.Mode) File.OpenError!fd_t {
456 switch (builtin.os) {
457 .macosx, .linux, .freebsd, .netbsd, .dragonfly => {
458 const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0;
459 const flags = O_LARGEFILE | os.O_RDWR | os.O_CREAT | os.O_CLOEXEC;
460 return openPosix(path, flags, mode);
461 },
462
463 .windows => return windows.CreateFile(
464 path,
465 windows.GENERIC_WRITE | windows.GENERIC_READ,
466 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
467 null,
468 windows.OPEN_ALWAYS,
469 windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED,
470 null,
471 ),
472
473 else => @compileError("Unsupported OS"),
474 }
475}
476
477/// This abstraction helps to close file handles in defer expressions
478/// without the possibility of failure and without the use of suspend points.
479/// Start a `CloseOperation` before opening a file, so that you can defer
480/// `CloseOperation.finish`.
481/// If you call `setHandle` then finishing will close the fd; otherwise finishing
482/// will deallocate the `CloseOperation`.
483pub const CloseOperation = struct {
484 allocator: *Allocator,
485 os_data: OsData,
486
487 const OsData = switch (builtin.os) {
488 .linux, .macosx, .freebsd, .netbsd, .dragonfly => OsDataPosix,
489
490 .windows => struct {
491 handle: ?fd_t,
492 },
493
494 else => @compileError("Unsupported OS"),
495 };
496
497 const OsDataPosix = struct {
498 have_fd: bool,
499 close_req_node: RequestNode,
500 };
501
502 pub fn start(allocator: *Allocator) (error{OutOfMemory}!*CloseOperation) {
503 const self = try allocator.create(CloseOperation);
504 self.* = CloseOperation{
505 .allocator = allocator,
506 .os_data = switch (builtin.os) {
507 .linux, .macosx, .freebsd, .netbsd, .dragonfly => initOsDataPosix(self),
508 .windows => OsData{ .handle = null },
509 else => @compileError("Unsupported OS"),
510 },
511 };
512 return self;
513 }
514
515 fn initOsDataPosix(self: *CloseOperation) OsData {
516 return OsData{
517 .have_fd = false,
518 .close_req_node = RequestNode{
519 .prev = null,
520 .next = null,
521 .data = Request{
522 .msg = Request.Msg{
523 .Close = Request.Msg.Close{ .fd = undefined },
524 },
525 .finish = Request.Finish{ .DeallocCloseOperation = self },
526 },
527 },
528 };
529 }
530
531 /// Defer this after creating.
532 pub fn finish(self: *CloseOperation) void {
533 switch (builtin.os) {
534 .linux,
535 .macosx,
536 .freebsd,
537 .netbsd,
538 .dragonfly,
539 => {
540 if (self.os_data.have_fd) {
541 global_event_loop.posixFsRequest(&self.os_data.close_req_node);
542 } else {
543 self.allocator.destroy(self);
544 }
545 },
546 .windows => {
547 if (self.os_data.handle) |handle| {
548 os.close(handle);
549 }
550 self.allocator.destroy(self);
551 },
552 else => @compileError("Unsupported OS"),
553 }
554 }
555
556 pub fn setHandle(self: *CloseOperation, handle: fd_t) void {
557 switch (builtin.os) {
558 .linux,
559 .macosx,
560 .freebsd,
561 .netbsd,
562 .dragonfly,
563 => {
564 self.os_data.close_req_node.data.msg.Close.fd = handle;
565 self.os_data.have_fd = true;
566 },
567 .windows => {
568 self.os_data.handle = handle;
569 },
570 else => @compileError("Unsupported OS"),
571 }
572 }
573
574 /// Undo a `setHandle`.
575 pub fn clearHandle(self: *CloseOperation) void {
576 switch (builtin.os) {
577 .linux,
578 .macosx,
579 .freebsd,
580 .netbsd,
581 .dragonfly,
582 => {
583 self.os_data.have_fd = false;
584 },
585 .windows => {
586 self.os_data.handle = null;
587 },
588 else => @compileError("Unsupported OS"),
589 }
590 }
591
592 pub fn getHandle(self: *CloseOperation) fd_t {
593 switch (builtin.os) {
594 .linux,
595 .macosx,
596 .freebsd,
597 .netbsd,
598 .dragonfly,
599 => {
600 assert(self.os_data.have_fd);
601 return self.os_data.close_req_node.data.msg.Close.fd;
602 },
603 .windows => {
604 return self.os_data.handle.?;
605 },
606 else => @compileError("Unsupported OS"),
607 }
608 }
609};
610
611/// contents must remain alive until writeFile completes.
612/// TODO make this atomic or provide writeFileAtomic and rename this one to writeFileTruncate
613pub fn writeFile(allocator: *Allocator, path: []const u8, contents: []const u8) !void {
614 return writeFileMode(allocator, path, contents, File.default_mode);
615}
616
617/// contents must remain alive until writeFile completes.
618pub fn writeFileMode(allocator: *Allocator, path: []const u8, contents: []const u8, mode: File.Mode) !void {
619 switch (builtin.os) {
620 .linux,
621 .macosx,
622 .freebsd,
623 .netbsd,
624 .dragonfly,
625 => return writeFileModeThread(allocator, path, contents, mode),
626 .windows => return writeFileWindows(path, contents),
627 else => @compileError("Unsupported OS"),
628 }
629}
630
631fn writeFileWindows(path: []const u8, contents: []const u8) !void {
632 const handle = try windows.CreateFile(
633 path,
634 windows.GENERIC_WRITE,
635 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
636 null,
637 windows.CREATE_ALWAYS,
638 windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED,
639 null,
640 );
641 defer os.close(handle);
642
643 try pwriteWindows(handle, contents, 0);
644}
645
646fn writeFileModeThread(allocator: *Allocator, path: []const u8, contents: []const u8, mode: File.Mode) !void {
647 const path_with_null = try std.cstr.addNullByte(allocator, path);
648 defer allocator.free(path_with_null);
649
650 var req_node = RequestNode{
651 .prev = null,
652 .next = null,
653 .data = Request{
654 .msg = Request.Msg{
655 .WriteFile = Request.Msg.WriteFile{
656 .path = path_with_null[0..path.len],
657 .contents = contents,
658 .mode = mode,
659 .result = undefined,
660 },
661 },
662 .finish = Request.Finish{
663 .TickNode = Loop.NextTickNode{
664 .prev = null,
665 .next = null,
666 .data = @frame(),
667 },
668 },
669 },
670 };
671
672 errdefer global_event_loop.posixFsCancel(&req_node);
673
674 suspend {
675 global_event_loop.posixFsRequest(&req_node);
676 }
677
678 return req_node.data.msg.WriteFile.result;
679}
680
681/// The frame resumes when the last data has been confirmed written, but before the file handle
682/// is closed.
683/// Caller owns returned memory.
684pub fn readFile(allocator: *Allocator, file_path: []const u8, max_size: usize) ![]u8 {
685 var close_op = try CloseOperation.start(allocator);
686 defer close_op.finish();
687
688 const fd = try openRead(file_path);
689 close_op.setHandle(fd);
690
691 var list = std.ArrayList(u8).init(allocator);
692 defer list.deinit();
693
694 while (true) {
695 try list.ensureCapacity(list.len + mem.page_size);
696 const buf = list.items[list.len..];
697 const buf_array = [_][]u8{buf};
698 const amt = try preadv(allocator, fd, &buf_array, list.len);
699 list.len += amt;
700 if (list.len > max_size) {
701 return error.FileTooBig;
702 }
703 if (amt < buf.len) {
704 return list.toOwnedSlice();
705 }
706 }
707}
708
709pub const WatchEventId = enum {
710 CloseWrite,
711 Delete,
712};
713
714fn eqlString(a: []const u16, b: []const u16) bool {
715 if (a.len != b.len) return false;
716 if (a.ptr == b.ptr) return true;
717 return mem.compare(u16, a, b) == .Equal;
718}
719
720fn hashString(s: []const u16) u32 {
721 return @truncate(u32, std.hash.Wyhash.hash(0, @sliceToBytes(s)));
722}
723
724pub const WatchEventError = error{
725 UserResourceLimitReached,
726 SystemResources,
727 AccessDenied,
728 Unexpected, // TODO remove this possibility
729};
730
731pub fn Watch(comptime V: type) type {
732 return struct {
733 channel: *event.Channel(Event.Error!Event),
734 os_data: OsData,
735 allocator: *Allocator,
736
737 const OsData = switch (builtin.os) {
738 // TODO https://github.com/ziglang/zig/issues/3778
739 .macosx, .freebsd, .netbsd, .dragonfly => KqOsData,
740 .linux => LinuxOsData,
741 .windows => WindowsOsData,
742
743 else => @compileError("Unsupported OS"),
744 };
745
746 const KqOsData = struct {
747 file_table: FileTable,
748 table_lock: event.Lock,
749
750 const FileTable = std.StringHashMap(*Put);
751 const Put = struct {
752 putter_frame: @Frame(kqPutEvents),
753 cancelled: bool = false,
754 value: V,
755 };
756 };
757
758 const WindowsOsData = struct {
759 table_lock: event.Lock,
760 dir_table: DirTable,
761 all_putters: std.atomic.Queue(Put),
762 ref_count: std.atomic.Int(usize),
763
764 const Put = struct {
765 putter: anyframe,
766 cancelled: bool = false,
767 };
768
769 const DirTable = std.StringHashMap(*Dir);
770 const FileTable = std.HashMap([]const u16, V, hashString, eqlString);
771
772 const Dir = struct {
773 putter_frame: @Frame(windowsDirReader),
774 file_table: FileTable,
775 table_lock: event.Lock,
776 };
777 };
778
779 const LinuxOsData = struct {
780 putter_frame: @Frame(linuxEventPutter),
781 inotify_fd: i32,
782 wd_table: WdTable,
783 table_lock: event.Lock,
784 cancelled: bool = false,
785
786 const WdTable = std.AutoHashMap(i32, Dir);
787 const FileTable = std.StringHashMap(V);
788
789 const Dir = struct {
790 dirname: []const u8,
791 file_table: FileTable,
792 };
793 };
794
795 const Self = @This();
796
797 pub const Event = struct {
798 id: Id,
799 data: V,
800
801 pub const Id = WatchEventId;
802 pub const Error = WatchEventError;
803 };
804
805 pub fn init(allocator: *Allocator, event_buf_count: usize) !*Self {
806 const channel = try allocator.create(event.Channel(Event.Error!Event));
807 errdefer allocator.destroy(channel);
808 var buf = try allocator.alloc(Event.Error!Event, event_buf_count);
809 errdefer allocator.free(buf);
810 channel.init(buf);
811 errdefer channel.deinit();
812
813 const self = try allocator.create(Self);
814 errdefer allocator.destroy(self);
815
816 switch (builtin.os) {
817 .linux => {
818 const inotify_fd = try os.inotify_init1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC);
819 errdefer os.close(inotify_fd);
820
821 self.* = Self{
822 .allocator = allocator,
823 .channel = channel,
824 .os_data = OsData{
825 .putter_frame = undefined,
826 .inotify_fd = inotify_fd,
827 .wd_table = OsData.WdTable.init(allocator),
828 .table_lock = event.Lock.init(),
829 },
830 };
831
832 self.os_data.putter_frame = async self.linuxEventPutter();
833 return self;
834 },
835
836 .windows => {
837 self.* = Self{
838 .allocator = allocator,
839 .channel = channel,
840 .os_data = OsData{
841 .table_lock = event.Lock.init(),
842 .dir_table = OsData.DirTable.init(allocator),
843 .ref_count = std.atomic.Int(usize).init(1),
844 .all_putters = std.atomic.Queue(anyframe).init(),
845 },
846 };
847 return self;
848 },
849
850 .macosx, .freebsd, .netbsd, .dragonfly => {
851 self.* = Self{
852 .allocator = allocator,
853 .channel = channel,
854 .os_data = OsData{
855 .table_lock = event.Lock.init(),
856 .file_table = OsData.FileTable.init(allocator),
857 },
858 };
859 return self;
860 },
861 else => @compileError("Unsupported OS"),
862 }
863 }
864
865 /// All addFile calls and removeFile calls must have completed.
866 pub fn deinit(self: *Self) void {
867 switch (builtin.os) {
868 .macosx, .freebsd, .netbsd, .dragonfly => {
869 // TODO we need to cancel the frames before destroying the lock
870 self.os_data.table_lock.deinit();
871 var it = self.os_data.file_table.iterator();
872 while (it.next()) |entry| {
873 entry.cancelled = true;
874 await entry.value.putter;
875 self.allocator.free(entry.key);
876 self.allocator.free(entry.value);
877 }
878 self.channel.deinit();
879 self.allocator.destroy(self.channel.buffer_nodes);
880 self.allocator.destroy(self);
881 },
882 .linux => {
883 self.os_data.cancelled = true;
884 await self.os_data.putter_frame;
885 self.allocator.destroy(self);
886 },
887 .windows => {
888 while (self.os_data.all_putters.get()) |putter_node| {
889 putter_node.cancelled = true;
890 await putter_node.frame;
891 }
892 self.deref();
893 },
894 else => @compileError("Unsupported OS"),
895 }
896 }
897
898 fn ref(self: *Self) void {
899 _ = self.os_data.ref_count.incr();
900 }
901
902 fn deref(self: *Self) void {
903 if (self.os_data.ref_count.decr() == 1) {
904 self.os_data.table_lock.deinit();
905 var it = self.os_data.dir_table.iterator();
906 while (it.next()) |entry| {
907 self.allocator.free(entry.key);
908 self.allocator.destroy(entry.value);
909 }
910 self.os_data.dir_table.deinit();
911 self.channel.deinit();
912 self.allocator.destroy(self.channel.buffer_nodes);
913 self.allocator.destroy(self);
914 }
915 }
916
917 pub fn addFile(self: *Self, file_path: []const u8, value: V) !?V {
918 switch (builtin.os) {
919 .macosx, .freebsd, .netbsd, .dragonfly => return addFileKEvent(self, file_path, value),
920 .linux => return addFileLinux(self, file_path, value),
921 .windows => return addFileWindows(self, file_path, value),
922 else => @compileError("Unsupported OS"),
923 }
924 }
925
926 fn addFileKEvent(self: *Self, file_path: []const u8, value: V) !?V {
927 const resolved_path = try std.fs.path.resolve(self.allocator, [_][]const u8{file_path});
928 var resolved_path_consumed = false;
929 defer if (!resolved_path_consumed) self.allocator.free(resolved_path);
930
931 var close_op = try CloseOperation.start(self.allocator);
932 var close_op_consumed = false;
933 defer if (!close_op_consumed) close_op.finish();
934
935 const flags = if (comptime std.Target.current.isDarwin()) os.O_SYMLINK | os.O_EVTONLY else 0;
936 const mode = 0;
937 const fd = try openPosix(self.allocator, resolved_path, flags, mode);
938 close_op.setHandle(fd);
939
940 var put = try self.allocator.create(OsData.Put);
941 errdefer self.allocator.destroy(put);
942 put.* = OsData.Put{
943 .value = value,
944 .putter_frame = undefined,
945 };
946 put.putter_frame = async self.kqPutEvents(close_op, put);
947 close_op_consumed = true;
948 errdefer {
949 put.cancelled = true;
950 await put.putter_frame;
951 }
952
953 const result = blk: {
954 const held = self.os_data.table_lock.acquire();
955 defer held.release();
956
957 const gop = try self.os_data.file_table.getOrPut(resolved_path);
958 if (gop.found_existing) {
959 const prev_value = gop.kv.value.value;
960 await gop.kv.value.putter_frame;
961 gop.kv.value = put;
962 break :blk prev_value;
963 } else {
964 resolved_path_consumed = true;
965 gop.kv.value = put;
966 break :blk null;
967 }
968 };
969
970 return result;
971 }
972
973 fn kqPutEvents(self: *Self, close_op: *CloseOperation, put: *OsData.Put) void {
974 global_event_loop.beginOneEvent();
975
976 defer {
977 close_op.finish();
978 global_event_loop.finishOneEvent();
979 }
980
981 while (!put.cancelled) {
982 if (global_event_loop.bsdWaitKev(
983 @intCast(usize, close_op.getHandle()),
984 os.EVFILT_VNODE,
985 os.NOTE_WRITE | os.NOTE_DELETE,
986 )) |kev| {
987 // TODO handle EV_ERROR
988 if (kev.fflags & os.NOTE_DELETE != 0) {
989 self.channel.put(Self.Event{
990 .id = Event.Id.Delete,
991 .data = put.value,
992 });
993 } else if (kev.fflags & os.NOTE_WRITE != 0) {
994 self.channel.put(Self.Event{
995 .id = Event.Id.CloseWrite,
996 .data = put.value,
997 });
998 }
999 } else |err| switch (err) {
1000 error.EventNotFound => unreachable,
1001 error.ProcessNotFound => unreachable,
1002 error.Overflow => unreachable,
1003 error.AccessDenied, error.SystemResources => |casted_err| {
1004 self.channel.put(casted_err);
1005 },
1006 }
1007 }
1008 }
1009
1010 fn addFileLinux(self: *Self, file_path: []const u8, value: V) !?V {
1011 const dirname = std.fs.path.dirname(file_path) orelse ".";
1012 const dirname_with_null = try std.cstr.addNullByte(self.allocator, dirname);
1013 var dirname_with_null_consumed = false;
1014 defer if (!dirname_with_null_consumed) self.channel.free(dirname_with_null);
1015
1016 const basename = std.fs.path.basename(file_path);
1017 const basename_with_null = try std.cstr.addNullByte(self.allocator, basename);
1018 var basename_with_null_consumed = false;
1019 defer if (!basename_with_null_consumed) self.allocator.free(basename_with_null);
1020
1021 const wd = try os.inotify_add_watchC(
1022 self.os_data.inotify_fd,
1023 dirname_with_null.ptr,
1024 os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_EXCL_UNLINK,
1025 );
1026 // wd is either a newly created watch or an existing one.
1027
1028 const held = self.os_data.table_lock.acquire();
1029 defer held.release();
1030
1031 const gop = try self.os_data.wd_table.getOrPut(wd);
1032 if (!gop.found_existing) {
1033 gop.kv.value = OsData.Dir{
1034 .dirname = dirname_with_null,
1035 .file_table = OsData.FileTable.init(self.allocator),
1036 };
1037 dirname_with_null_consumed = true;
1038 }
1039 const dir = &gop.kv.value;
1040
1041 const file_table_gop = try dir.file_table.getOrPut(basename_with_null);
1042 if (file_table_gop.found_existing) {
1043 const prev_value = file_table_gop.kv.value;
1044 file_table_gop.kv.value = value;
1045 return prev_value;
1046 } else {
1047 file_table_gop.kv.value = value;
1048 basename_with_null_consumed = true;
1049 return null;
1050 }
1051 }
1052
1053 fn addFileWindows(self: *Self, file_path: []const u8, value: V) !?V {
1054 // TODO we might need to convert dirname and basename to canonical file paths ("short"?)
1055 const dirname = try std.mem.dupe(self.allocator, u8, std.fs.path.dirname(file_path) orelse ".");
1056 var dirname_consumed = false;
1057 defer if (!dirname_consumed) self.allocator.free(dirname);
1058
1059 const dirname_utf16le = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, dirname);
1060 defer self.allocator.free(dirname_utf16le);
1061
1062 // TODO https://github.com/ziglang/zig/issues/265
1063 const basename = std.fs.path.basename(file_path);
1064 const basename_utf16le_null = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, basename);
1065 var basename_utf16le_null_consumed = false;
1066 defer if (!basename_utf16le_null_consumed) self.allocator.free(basename_utf16le_null);
1067 const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1];
1068
1069 const dir_handle = try windows.CreateFileW(
1070 dirname_utf16le.ptr,
1071 windows.FILE_LIST_DIRECTORY,
1072 windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE | windows.FILE_SHARE_WRITE,
1073 null,
1074 windows.OPEN_EXISTING,
1075 windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OVERLAPPED,
1076 null,
1077 );
1078 var dir_handle_consumed = false;
1079 defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle);
1080
1081 const held = self.os_data.table_lock.acquire();
1082 defer held.release();
1083
1084 const gop = try self.os_data.dir_table.getOrPut(dirname);
1085 if (gop.found_existing) {
1086 const dir = gop.kv.value;
1087 const held_dir_lock = dir.table_lock.acquire();
1088 defer held_dir_lock.release();
1089
1090 const file_gop = try dir.file_table.getOrPut(basename_utf16le_no_null);
1091 if (file_gop.found_existing) {
1092 const prev_value = file_gop.kv.value;
1093 file_gop.kv.value = value;
1094 return prev_value;
1095 } else {
1096 file_gop.kv.value = value;
1097 basename_utf16le_null_consumed = true;
1098 return null;
1099 }
1100 } else {
1101 errdefer _ = self.os_data.dir_table.remove(dirname);
1102 const dir = try self.allocator.create(OsData.Dir);
1103 errdefer self.allocator.destroy(dir);
1104
1105 dir.* = OsData.Dir{
1106 .file_table = OsData.FileTable.init(self.allocator),
1107 .table_lock = event.Lock.init(),
1108 .putter_frame = undefined,
1109 };
1110 gop.kv.value = dir;
1111 assert((try dir.file_table.put(basename_utf16le_no_null, value)) == null);
1112 basename_utf16le_null_consumed = true;
1113
1114 dir.putter_frame = async self.windowsDirReader(dir_handle, dir);
1115 dir_handle_consumed = true;
1116
1117 dirname_consumed = true;
1118
1119 return null;
1120 }
1121 }
1122
1123 fn windowsDirReader(self: *Self, dir_handle: windows.HANDLE, dir: *OsData.Dir) void {
1124 self.ref();
1125 defer self.deref();
1126
1127 defer os.close(dir_handle);
1128
1129 var putter_node = std.atomic.Queue(anyframe).Node{
1130 .data = .{ .putter = @frame() },
1131 .prev = null,
1132 .next = null,
1133 };
1134 self.os_data.all_putters.put(&putter_node);
1135 defer _ = self.os_data.all_putters.remove(&putter_node);
1136
1137 var resume_node = Loop.ResumeNode.Basic{
1138 .base = Loop.ResumeNode{
1139 .id = Loop.ResumeNode.Id.Basic,
1140 .handle = @frame(),
1141 .overlapped = windows.OVERLAPPED{
1142 .Internal = 0,
1143 .InternalHigh = 0,
1144 .Offset = 0,
1145 .OffsetHigh = 0,
1146 .hEvent = null,
1147 },
1148 },
1149 };
1150 var event_buf: [4096]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined;
1151
1152 // TODO handle this error not in the channel but in the setup
1153 _ = windows.CreateIoCompletionPort(
1154 dir_handle,
1155 global_event_loop.os_data.io_port,
1156 undefined,
1157 undefined,
1158 ) catch |err| {
1159 self.channel.put(err);
1160 return;
1161 };
1162
1163 while (!putter_node.data.cancelled) {
1164 {
1165 // TODO only 1 beginOneEvent for the whole function
1166 global_event_loop.beginOneEvent();
1167 errdefer global_event_loop.finishOneEvent();
1168 errdefer {
1169 _ = windows.kernel32.CancelIoEx(dir_handle, &resume_node.base.overlapped);
1170 }
1171 suspend {
1172 _ = windows.kernel32.ReadDirectoryChangesW(
1173 dir_handle,
1174 &event_buf,
1175 @intCast(windows.DWORD, event_buf.len),
1176 windows.FALSE, // watch subtree
1177 windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME |
1178 windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE |
1179 windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS |
1180 windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY,
1181 null, // number of bytes transferred (unused for async)
1182 &resume_node.base.overlapped,
1183 null, // completion routine - unused because we use IOCP
1184 );
1185 }
1186 }
1187 var bytes_transferred: windows.DWORD = undefined;
1188 if (windows.kernel32.GetOverlappedResult(dir_handle, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) {
1189 const err = switch (windows.kernel32.GetLastError()) {
1190 else => |err| windows.unexpectedError(err),
1191 };
1192 self.channel.put(err);
1193 } else {
1194 // can't use @bytesToSlice because of the special variable length name field
1195 var ptr = event_buf[0..].ptr;
1196 const end_ptr = ptr + bytes_transferred;
1197 var ev: *windows.FILE_NOTIFY_INFORMATION = undefined;
1198 while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += ev.NextEntryOffset) {
1199 ev = @ptrCast(*windows.FILE_NOTIFY_INFORMATION, ptr);
1200 const emit = switch (ev.Action) {
1201 windows.FILE_ACTION_REMOVED => WatchEventId.Delete,
1202 windows.FILE_ACTION_MODIFIED => WatchEventId.CloseWrite,
1203 else => null,
1204 };
1205 if (emit) |id| {
1206 const basename_utf16le = ([*]u16)(&ev.FileName)[0 .. ev.FileNameLength / 2];
1207 const user_value = blk: {
1208 const held = dir.table_lock.acquire();
1209 defer held.release();
1210
1211 if (dir.file_table.get(basename_utf16le)) |entry| {
1212 break :blk entry.value;
1213 } else {
1214 break :blk null;
1215 }
1216 };
1217 if (user_value) |v| {
1218 self.channel.put(Event{
1219 .id = id,
1220 .data = v,
1221 });
1222 }
1223 }
1224 if (ev.NextEntryOffset == 0) break;
1225 }
1226 }
1227 }
1228 }
1229
1230 pub fn removeFile(self: *Self, file_path: []const u8) ?V {
1231 @panic("TODO");
1232 }
1233
1234 fn linuxEventPutter(self: *Self) void {
1235 global_event_loop.beginOneEvent();
1236
1237 defer {
1238 self.os_data.table_lock.deinit();
1239 var wd_it = self.os_data.wd_table.iterator();
1240 while (wd_it.next()) |wd_entry| {
1241 var file_it = wd_entry.value.file_table.iterator();
1242 while (file_it.next()) |file_entry| {
1243 self.allocator.free(file_entry.key);
1244 }
1245 self.allocator.free(wd_entry.value.dirname);
1246 wd_entry.value.file_table.deinit();
1247 }
1248 self.os_data.wd_table.deinit();
1249 global_event_loop.finishOneEvent();
1250 os.close(self.os_data.inotify_fd);
1251 self.channel.deinit();
1252 self.allocator.free(self.channel.buffer_nodes);
1253 }
1254
1255 var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined;
1256
1257 while (!self.os_data.cancelled) {
1258 const rc = os.linux.read(self.os_data.inotify_fd, &event_buf, event_buf.len);
1259 const errno = os.linux.getErrno(rc);
1260 switch (errno) {
1261 0 => {
1262 // can't use @bytesToSlice because of the special variable length name field
1263 var ptr = event_buf[0..].ptr;
1264 const end_ptr = ptr + event_buf.len;
1265 var ev: *os.linux.inotify_event = undefined;
1266 while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) {
1267 ev = @ptrCast(*os.linux.inotify_event, ptr);
1268 if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) {
1269 const basename_ptr = ptr + @sizeOf(os.linux.inotify_event);
1270 // `ev.len` counts all bytes in `ev.name` including terminating null byte.
1271 const basename_with_null = basename_ptr[0..ev.len];
1272 const user_value = blk: {
1273 const held = self.os_data.table_lock.acquire();
1274 defer held.release();
1275
1276 const dir = &self.os_data.wd_table.get(ev.wd).?.value;
1277 if (dir.file_table.get(basename_with_null)) |entry| {
1278 break :blk entry.value;
1279 } else {
1280 break :blk null;
1281 }
1282 };
1283 if (user_value) |v| {
1284 self.channel.put(Event{
1285 .id = WatchEventId.CloseWrite,
1286 .data = v,
1287 });
1288 }
1289 }
1290
1291 ptr = @alignCast(@alignOf(os.linux.inotify_event), ptr + @sizeOf(os.linux.inotify_event) + ev.len);
1292 }
1293 },
1294 os.linux.EINTR => continue,
1295 os.linux.EINVAL => unreachable,
1296 os.linux.EFAULT => unreachable,
1297 os.linux.EAGAIN => {
1298 global_event_loop.linuxWaitFd(self.os_data.inotify_fd, os.linux.EPOLLET | os.linux.EPOLLIN | os.EPOLLONESHOT);
1299 },
1300 else => unreachable,
1301 }
1302 }
1303 }
1304 };
1305}
1306
1307const test_tmp_dir = "std_event_fs_test";
1308
1309test "write a file, watch it, write it again" {
1310 // TODO provide a way to run tests in evented I/O mode
1311 if (!std.io.is_async) return error.SkipZigTest;
1312
1313 const allocator = std.heap.page_allocator;
1314
1315 // TODO move this into event loop too
1316 try os.makePath(allocator, test_tmp_dir);
1317 defer os.deleteTree(test_tmp_dir) catch {};
1318
1319 return testFsWatch(&allocator);
1320}
1321
1322fn testFsWatch(allocator: *Allocator) !void {
1323 const file_path = try std.fs.path.join(allocator, [_][]const u8{ test_tmp_dir, "file.txt" });
1324 defer allocator.free(file_path);
1325
1326 const contents =
1327 \\line 1
1328 \\line 2
1329 ;
1330 const line2_offset = 7;
1331
1332 // first just write then read the file
1333 try writeFile(allocator, file_path, contents);
1334
1335 const read_contents = try readFile(allocator, file_path, 1024 * 1024);
1336 testing.expectEqualSlices(u8, contents, read_contents);
1337
1338 // now watch the file
1339 var watch = try Watch(void).init(allocator, 0);
1340 defer watch.deinit();
1341
1342 testing.expect((try watch.addFile(file_path, {})) == null);
1343
1344 const ev = watch.channel.get();
1345 var ev_consumed = false;
1346 defer if (!ev_consumed) await ev;
1347
1348 // overwrite line 2
1349 const fd = try await openReadWrite(file_path, File.default_mode);
1350 {
1351 defer os.close(fd);
1352
1353 try pwritev(allocator, fd, []const []const u8{"lorem ipsum"}, line2_offset);
1354 }
1355
1356 ev_consumed = true;
1357 switch ((try await ev).id) {
1358 WatchEventId.CloseWrite => {},
1359 WatchEventId.Delete => @panic("wrong event"),
1360 }
1361 const contents_updated = try readFile(allocator, file_path, 1024 * 1024);
1362 testing.expectEqualSlices(u8,
1363 \\line 1
1364 \\lorem ipsum
1365 , contents_updated);
1366
1367 // TODO test deleting the file and then re-adding it. we should get events for both
1368}
1369
1370pub const OutStream = struct {
1371 fd: fd_t,
1372 stream: Stream,
1373 allocator: *Allocator,
1374 offset: usize,
1375
1376 pub const Error = File.WriteError;
1377 pub const Stream = event.io.OutStream(Error);
1378
1379 pub fn init(allocator: *Allocator, fd: fd_t, offset: usize) OutStream {
1380 return OutStream{
1381 .fd = fd,
1382 .offset = offset,
1383 .stream = Stream{ .writeFn = writeFn },
1384 };
1385 }
1386
1387 fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void {
1388 const self = @fieldParentPtr(OutStream, "stream", out_stream);
1389 const offset = self.offset;
1390 self.offset += bytes.len;
1391 return pwritev(self.allocator, self.fd, [_][]const u8{bytes}, offset);
1392 }
1393};
1394
1395pub const InStream = struct {
1396 fd: fd_t,
1397 stream: Stream,
1398 allocator: *Allocator,
1399 offset: usize,
1400
1401 pub const Error = PReadVError; // TODO make this not have OutOfMemory
1402 pub const Stream = event.io.InStream(Error);
1403
1404 pub fn init(allocator: *Allocator, fd: fd_t, offset: usize) InStream {
1405 return InStream{
1406 .fd = fd,
1407 .offset = offset,
1408 .stream = Stream{ .readFn = readFn },
1409 };
1410 }
1411
1412 fn readFn(in_stream: *Stream, bytes: []u8) Error!usize {
1413 const self = @fieldParentPtr(InStream, "stream", in_stream);
1414 const amt = try preadv(self.allocator, self.fd, [_][]u8{bytes}, self.offset);
1415 self.offset += amt;
1416 return amt;
1417 }
1418};
lib/std/event/loop.zig+317-50
...@@ -6,7 +6,6 @@ const testing = std.testing;...@@ -6,7 +6,6 @@ const testing = std.testing;
6const mem = std.mem;6const mem = std.mem;
7const AtomicRmwOp = builtin.AtomicRmwOp;7const AtomicRmwOp = builtin.AtomicRmwOp;
8const AtomicOrder = builtin.AtomicOrder;8const AtomicOrder = builtin.AtomicOrder;
9const fs = std.event.fs;
10const os = std.os;9const os = std.os;
11const windows = os.windows;10const windows = os.windows;
12const maxInt = std.math.maxInt;11const maxInt = std.math.maxInt;
...@@ -174,21 +173,19 @@ pub const Loop = struct {...@@ -174,21 +173,19 @@ pub const Loop = struct {
174 fn initOsData(self: *Loop, extra_thread_count: usize) InitOsDataError!void {173 fn initOsData(self: *Loop, extra_thread_count: usize) InitOsDataError!void {
175 switch (builtin.os) {174 switch (builtin.os) {
176 .linux => {175 .linux => {
177 self.os_data.fs_queue = std.atomic.Queue(fs.Request).init();176 self.os_data.fs_queue = std.atomic.Queue(Request).init();
178 self.os_data.fs_queue_item = 0;177 self.os_data.fs_queue_item = 0;
179 // we need another thread for the file system because Linux does not have an async178 // we need another thread for the file system because Linux does not have an async
180 // file system I/O API.179 // file system I/O API.
181 self.os_data.fs_end_request = fs.RequestNode{180 self.os_data.fs_end_request = Request.Node{
182 .prev = undefined,181 .data = Request{
183 .next = undefined,182 .msg = .end,
184 .data = fs.Request{183 .finish = .NoAction,
185 .msg = fs.Request.Msg.End,
186 .finish = fs.Request.Finish.NoAction,
187 },184 },
188 };185 };
189186
190 errdefer {187 errdefer {
191 while (self.available_eventfd_resume_nodes.pop()) |node| os.close(node.data.eventfd);188 while (self.available_eventfd_resume_nodes.pop()) |node| noasync os.close(node.data.eventfd);
192 }189 }
193 for (self.eventfd_resume_nodes) |*eventfd_node| {190 for (self.eventfd_resume_nodes) |*eventfd_node| {
194 eventfd_node.* = std.atomic.Stack(ResumeNode.EventFd).Node{191 eventfd_node.* = std.atomic.Stack(ResumeNode.EventFd).Node{
...@@ -207,10 +204,10 @@ pub const Loop = struct {...@@ -207,10 +204,10 @@ pub const Loop = struct {
207 }204 }
208205
209 self.os_data.epollfd = try os.epoll_create1(os.EPOLL_CLOEXEC);206 self.os_data.epollfd = try os.epoll_create1(os.EPOLL_CLOEXEC);
210 errdefer os.close(self.os_data.epollfd);207 errdefer noasync os.close(self.os_data.epollfd);
211208
212 self.os_data.final_eventfd = try os.eventfd(0, os.EFD_CLOEXEC | os.EFD_NONBLOCK);209 self.os_data.final_eventfd = try os.eventfd(0, os.EFD_CLOEXEC | os.EFD_NONBLOCK);
213 errdefer os.close(self.os_data.final_eventfd);210 errdefer noasync os.close(self.os_data.final_eventfd);
214211
215 self.os_data.final_eventfd_event = os.epoll_event{212 self.os_data.final_eventfd_event = os.epoll_event{
216 .events = os.EPOLLIN,213 .events = os.EPOLLIN,
...@@ -237,7 +234,7 @@ pub const Loop = struct {...@@ -237,7 +234,7 @@ pub const Loop = struct {
237 var extra_thread_index: usize = 0;234 var extra_thread_index: usize = 0;
238 errdefer {235 errdefer {
239 // writing 8 bytes to an eventfd cannot fail236 // writing 8 bytes to an eventfd cannot fail
240 os.write(self.os_data.final_eventfd, &wakeup_bytes) catch unreachable;237 noasync os.write(self.os_data.final_eventfd, &wakeup_bytes) catch unreachable;
241 while (extra_thread_index != 0) {238 while (extra_thread_index != 0) {
242 extra_thread_index -= 1;239 extra_thread_index -= 1;
243 self.extra_threads[extra_thread_index].wait();240 self.extra_threads[extra_thread_index].wait();
...@@ -249,20 +246,20 @@ pub const Loop = struct {...@@ -249,20 +246,20 @@ pub const Loop = struct {
249 },246 },
250 .macosx, .freebsd, .netbsd, .dragonfly => {247 .macosx, .freebsd, .netbsd, .dragonfly => {
251 self.os_data.kqfd = try os.kqueue();248 self.os_data.kqfd = try os.kqueue();
252 errdefer os.close(self.os_data.kqfd);249 errdefer noasync os.close(self.os_data.kqfd);
253250
254 self.os_data.fs_kqfd = try os.kqueue();251 self.os_data.fs_kqfd = try os.kqueue();
255 errdefer os.close(self.os_data.fs_kqfd);252 errdefer noasync os.close(self.os_data.fs_kqfd);
256253
257 self.os_data.fs_queue = std.atomic.Queue(fs.Request).init();254 self.os_data.fs_queue = std.atomic.Queue(Request).init();
258 // we need another thread for the file system because Darwin does not have an async255 // we need another thread for the file system because Darwin does not have an async
259 // file system I/O API.256 // file system I/O API.
260 self.os_data.fs_end_request = fs.RequestNode{257 self.os_data.fs_end_request = Request.Node{
261 .prev = undefined,258 .prev = undefined,
262 .next = undefined,259 .next = undefined,
263 .data = fs.Request{260 .data = Request{
264 .msg = fs.Request.Msg.End,261 .msg = .end,
265 .finish = fs.Request.Finish.NoAction,262 .finish = .NoAction,
266 },263 },
267 };264 };
268265
...@@ -407,14 +404,14 @@ pub const Loop = struct {...@@ -407,14 +404,14 @@ pub const Loop = struct {
407 fn deinitOsData(self: *Loop) void {404 fn deinitOsData(self: *Loop) void {
408 switch (builtin.os) {405 switch (builtin.os) {
409 .linux => {406 .linux => {
410 os.close(self.os_data.final_eventfd);407 noasync os.close(self.os_data.final_eventfd);
411 while (self.available_eventfd_resume_nodes.pop()) |node| os.close(node.data.eventfd);408 while (self.available_eventfd_resume_nodes.pop()) |node| noasync os.close(node.data.eventfd);
412 os.close(self.os_data.epollfd);409 noasync os.close(self.os_data.epollfd);
413 self.allocator.free(self.eventfd_resume_nodes);410 self.allocator.free(self.eventfd_resume_nodes);
414 },411 },
415 .macosx, .freebsd, .netbsd, .dragonfly => {412 .macosx, .freebsd, .netbsd, .dragonfly => {
416 os.close(self.os_data.kqfd);413 noasync os.close(self.os_data.kqfd);
417 os.close(self.os_data.fs_kqfd);414 noasync os.close(self.os_data.fs_kqfd);
418 },415 },
419 .windows => {416 .windows => {
420 windows.CloseHandle(self.os_data.io_port);417 windows.CloseHandle(self.os_data.io_port);
...@@ -711,6 +708,190 @@ pub const Loop = struct {...@@ -711,6 +708,190 @@ pub const Loop = struct {
711 }708 }
712 }709 }
713710
711 /// Performs an async `os.open` using a separate thread.
712 pub fn openZ(self: *Loop, file_path: [*:0]const u8, flags: u32, mode: usize) os.OpenError!os.fd_t {
713 var req_node = Request.Node{
714 .data = .{
715 .msg = .{
716 .open = .{
717 .path = file_path,
718 .flags = flags,
719 .mode = mode,
720 .result = undefined,
721 },
722 },
723 .finish = .{ .TickNode = .{ .data = @frame() } },
724 },
725 };
726 suspend {
727 self.posixFsRequest(&req_node);
728 }
729 return req_node.data.msg.open.result;
730 }
731
732 /// Performs an async `os.opent` using a separate thread.
733 pub fn openatZ(self: *Loop, fd: os.fd_t, file_path: [*:0]const u8, flags: u32, mode: usize) os.OpenError!os.fd_t {
734 var req_node = Request.Node{
735 .data = .{
736 .msg = .{
737 .openat = .{
738 .fd = fd,
739 .path = file_path,
740 .flags = flags,
741 .mode = mode,
742 .result = undefined,
743 },
744 },
745 .finish = .{ .TickNode = .{ .data = @frame() } },
746 },
747 };
748 suspend {
749 self.posixFsRequest(&req_node);
750 }
751 return req_node.data.msg.openat.result;
752 }
753
754 /// Performs an async `os.close` using a separate thread.
755 pub fn close(self: *Loop, fd: os.fd_t) void {
756 var req_node = Request.Node{
757 .data = .{
758 .msg = .{ .close = .{ .fd = fd } },
759 .finish = .{ .TickNode = .{ .data = @frame() } },
760 },
761 };
762 suspend {
763 self.posixFsRequest(&req_node);
764 }
765 }
766
767 /// Performs an async `os.read` using a separate thread.
768 /// `fd` must block and not return EAGAIN.
769 pub fn read(self: *Loop, fd: os.fd_t, buf: []u8) os.ReadError!usize {
770 var req_node = Request.Node{
771 .data = .{
772 .msg = .{
773 .read = .{
774 .fd = fd,
775 .buf = buf,
776 .result = undefined,
777 },
778 },
779 .finish = .{ .TickNode = .{ .data = @frame() } },
780 },
781 };
782 suspend {
783 self.posixFsRequest(&req_node);
784 }
785 return req_node.data.msg.read.result;
786 }
787
788 /// Performs an async `os.readv` using a separate thread.
789 /// `fd` must block and not return EAGAIN.
790 pub fn readv(self: *Loop, fd: os.fd_t, iov: []const os.iovec) os.ReadError!usize {
791 var req_node = Request.Node{
792 .data = .{
793 .msg = .{
794 .readv = .{
795 .fd = fd,
796 .iov = iov,
797 .result = undefined,
798 },
799 },
800 .finish = .{ .TickNode = .{ .data = @frame() } },
801 },
802 };
803 suspend {
804 self.posixFsRequest(&req_node);
805 }
806 return req_node.data.msg.readv.result;
807 }
808
809 /// Performs an async `os.preadv` using a separate thread.
810 /// `fd` must block and not return EAGAIN.
811 pub fn preadv(self: *Loop, fd: os.fd_t, iov: []const os.iovec, offset: u64) os.ReadError!usize {
812 var req_node = Request.Node{
813 .data = .{
814 .msg = .{
815 .preadv = .{
816 .fd = fd,
817 .iov = iov,
818 .offset = offset,
819 .result = undefined,
820 },
821 },
822 .finish = .{ .TickNode = .{ .data = @frame() } },
823 },
824 };
825 suspend {
826 self.posixFsRequest(&req_node);
827 }
828 return req_node.data.msg.preadv.result;
829 }
830
831 /// Performs an async `os.write` using a separate thread.
832 /// `fd` must block and not return EAGAIN.
833 pub fn write(self: *Loop, fd: os.fd_t, bytes: []const u8) os.WriteError!void {
834 var req_node = Request.Node{
835 .data = .{
836 .msg = .{
837 .write = .{
838 .fd = fd,
839 .bytes = bytes,
840 .result = undefined,
841 },
842 },
843 .finish = .{ .TickNode = .{ .data = @frame() } },
844 },
845 };
846 suspend {
847 self.posixFsRequest(&req_node);
848 }
849 return req_node.data.msg.write.result;
850 }
851
852 /// Performs an async `os.writev` using a separate thread.
853 /// `fd` must block and not return EAGAIN.
854 pub fn writev(self: *Loop, fd: os.fd_t, iov: []const os.iovec_const) WriteError!void {
855 var req_node = Request.Node{
856 .data = .{
857 .msg = .{
858 .writev = .{
859 .fd = fd,
860 .iov = iov,
861 .result = undefined,
862 },
863 },
864 .finish = .{ .TickNode = .{ .data = @frame() } },
865 },
866 };
867 suspend {
868 self.posixFsRequest(&req_node);
869 }
870 return req_node.data.msg.writev.result;
871 }
872
873 /// Performs an async `os.pwritev` using a separate thread.
874 /// `fd` must block and not return EAGAIN.
875 pub fn pwritev(self: *Loop, fd: os.fd_t, iov: []const os.iovec_const, offset: u64) WriteError!void {
876 var req_node = Request.Node{
877 .data = .{
878 .msg = .{
879 .pwritev = .{
880 .fd = fd,
881 .iov = iov,
882 .offset = offset,
883 .result = undefined,
884 },
885 },
886 .finish = .{ .TickNode = .{ .data = @frame() } },
887 },
888 };
889 suspend {
890 self.posixFsRequest(&req_node);
891 }
892 return req_node.data.msg.pwritev.result;
893 }
894
714 fn workerRun(self: *Loop) void {895 fn workerRun(self: *Loop) void {
715 while (true) {896 while (true) {
716 while (true) {897 while (true) {
...@@ -804,7 +985,7 @@ pub const Loop = struct {...@@ -804,7 +985,7 @@ pub const Loop = struct {
804 }985 }
805 }986 }
806987
807 fn posixFsRequest(self: *Loop, request_node: *fs.RequestNode) void {988 fn posixFsRequest(self: *Loop, request_node: *Request.Node) void {
808 self.beginOneEvent(); // finished in posixFsRun after processing the msg989 self.beginOneEvent(); // finished in posixFsRun after processing the msg
809 self.os_data.fs_queue.put(request_node);990 self.os_data.fs_queue.put(request_node);
810 switch (builtin.os) {991 switch (builtin.os) {
...@@ -826,7 +1007,7 @@ pub const Loop = struct {...@@ -826,7 +1007,7 @@ pub const Loop = struct {
826 }1007 }
827 }1008 }
8281009
829 fn posixFsCancel(self: *Loop, request_node: *fs.RequestNode) void {1010 fn posixFsCancel(self: *Loop, request_node: *Request.Node) void {
830 if (self.os_data.fs_queue.remove(request_node)) {1011 if (self.os_data.fs_queue.remove(request_node)) {
831 self.finishOneEvent();1012 self.finishOneEvent();
832 }1013 }
...@@ -841,37 +1022,32 @@ pub const Loop = struct {...@@ -841,37 +1022,32 @@ pub const Loop = struct {
841 }1022 }
842 while (self.os_data.fs_queue.get()) |node| {1023 while (self.os_data.fs_queue.get()) |node| {
843 switch (node.data.msg) {1024 switch (node.data.msg) {
844 .End => return,1025 .end => return,
845 .WriteV => |*msg| {1026 .read => |*msg| {
1027 msg.result = noasync os.read(msg.fd, msg.buf);
1028 },
1029 .write => |*msg| {
1030 msg.result = noasync os.write(msg.fd, msg.bytes);
1031 },
1032 .writev => |*msg| {
846 msg.result = noasync os.writev(msg.fd, msg.iov);1033 msg.result = noasync os.writev(msg.fd, msg.iov);
847 },1034 },
848 .PWriteV => |*msg| {1035 .pwritev => |*msg| {
849 msg.result = noasync os.pwritev(msg.fd, msg.iov, msg.offset);1036 msg.result = noasync os.pwritev(msg.fd, msg.iov, msg.offset);
850 },1037 },
851 .PReadV => |*msg| {1038 .preadv => |*msg| {
852 msg.result = noasync os.preadv(msg.fd, msg.iov, msg.offset);1039 msg.result = noasync os.preadv(msg.fd, msg.iov, msg.offset);
853 },1040 },
854 .Open => |*msg| {1041 .open => |*msg| {
855 msg.result = noasync os.openC(msg.path.ptr, msg.flags, msg.mode);1042 msg.result = noasync os.openC(msg.path, msg.flags, msg.mode);
856 },1043 },
857 .Close => |*msg| noasync os.close(msg.fd),1044 .openat => |*msg| {
858 .WriteFile => |*msg| blk: {1045 msg.result = noasync os.openatC(msg.fd, msg.path, msg.flags, msg.mode);
859 const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0;
860 const flags = O_LARGEFILE | os.O_WRONLY | os.O_CREAT |
861 os.O_CLOEXEC | os.O_TRUNC;
862 const fd = noasync os.openC(msg.path.ptr, flags, msg.mode) catch |err| {
863 msg.result = err;
864 break :blk;
865 };
866 defer noasync os.close(fd);
867 msg.result = noasync os.write(fd, msg.contents);
868 },1046 },
1047 .close => |*msg| noasync os.close(msg.fd),
869 }1048 }
870 switch (node.data.finish) {1049 switch (node.data.finish) {
871 .TickNode => |*tick_node| self.onNextTick(tick_node),1050 .TickNode => |*tick_node| self.onNextTick(tick_node),
872 .DeallocCloseOperation => |close_op| {
873 self.allocator.destroy(close_op);
874 },
875 .NoAction => {},1051 .NoAction => {},
876 }1052 }
877 self.finishOneEvent();1053 self.finishOneEvent();
...@@ -911,8 +1087,8 @@ pub const Loop = struct {...@@ -911,8 +1087,8 @@ pub const Loop = struct {
911 fs_kevent_wait: os.Kevent,1087 fs_kevent_wait: os.Kevent,
912 fs_thread: *Thread,1088 fs_thread: *Thread,
913 fs_kqfd: i32,1089 fs_kqfd: i32,
914 fs_queue: std.atomic.Queue(fs.Request),1090 fs_queue: std.atomic.Queue(Request),
915 fs_end_request: fs.RequestNode,1091 fs_end_request: Request.Node,
916 };1092 };
9171093
918 const LinuxOsData = struct {1094 const LinuxOsData = struct {
...@@ -921,8 +1097,99 @@ pub const Loop = struct {...@@ -921,8 +1097,99 @@ pub const Loop = struct {
921 final_eventfd_event: os.linux.epoll_event,1097 final_eventfd_event: os.linux.epoll_event,
922 fs_thread: *Thread,1098 fs_thread: *Thread,
923 fs_queue_item: i32,1099 fs_queue_item: i32,
924 fs_queue: std.atomic.Queue(fs.Request),1100 fs_queue: std.atomic.Queue(Request),
925 fs_end_request: fs.RequestNode,1101 fs_end_request: Request.Node,
1102 };
1103
1104 pub const Request = struct {
1105 msg: Msg,
1106 finish: Finish,
1107
1108 pub const Node = std.atomic.Queue(Request).Node;
1109
1110 pub const Finish = union(enum) {
1111 TickNode: Loop.NextTickNode,
1112 NoAction,
1113 };
1114
1115 pub const Msg = union(enum) {
1116 read: Read,
1117 write: Write,
1118 writev: WriteV,
1119 pwritev: PWriteV,
1120 preadv: PReadV,
1121 open: Open,
1122 openat: OpenAt,
1123 close: Close,
1124
1125 /// special - means the fs thread should exit
1126 end,
1127
1128 pub const Read = struct {
1129 fd: os.fd_t,
1130 buf: []u8,
1131 result: Error!usize,
1132
1133 pub const Error = os.ReadError;
1134 };
1135
1136 pub const Write = struct {
1137 fd: os.fd_t,
1138 bytes: []const u8,
1139 result: Error!void,
1140
1141 pub const Error = os.WriteError;
1142 };
1143
1144 pub const WriteV = struct {
1145 fd: os.fd_t,
1146 iov: []const os.iovec_const,
1147 result: Error!void,
1148
1149 pub const Error = os.WriteError;
1150 };
1151
1152 pub const PWriteV = struct {
1153 fd: os.fd_t,
1154 iov: []const os.iovec_const,
1155 offset: usize,
1156 result: Error!void,
1157
1158 pub const Error = os.WriteError;
1159 };
1160
1161 pub const PReadV = struct {
1162 fd: os.fd_t,
1163 iov: []const os.iovec,
1164 offset: usize,
1165 result: Error!usize,
1166
1167 pub const Error = os.ReadError;
1168 };
1169
1170 pub const Open = struct {
1171 path: [*:0]const u8,
1172 flags: u32,
1173 mode: os.mode_t,
1174 result: Error!os.fd_t,
1175
1176 pub const Error = os.OpenError;
1177 };
1178
1179 pub const OpenAt = struct {
1180 fd: os.fd_t,
1181 path: [*:0]const u8,
1182 flags: u32,
1183 mode: os.mode_t,
1184 result: Error!os.fd_t,
1185
1186 pub const Error = os.OpenError;
1187 };
1188
1189 pub const Close = struct {
1190 fd: os.fd_t,
1191 };
1192 };
926 };1193 };
927};1194};
9281195
lib/std/fs.zig+31-6
...@@ -23,6 +23,8 @@ pub const realpathW = os.realpathW;...@@ -23,6 +23,8 @@ pub const realpathW = os.realpathW;
23pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir;23pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir;
24pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError;24pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError;
2525
26pub const Watch = @import("fs/watch.zig").Watch;
27
26/// This represents the maximum size of a UTF-8 encoded file path.28/// This represents the maximum size of a UTF-8 encoded file path.
27/// All file system operations which return a path are guaranteed to29/// All file system operations which return a path are guaranteed to
28/// fit into a UTF-8 encoded array of this length.30/// fit into a UTF-8 encoded array of this length.
...@@ -43,6 +45,13 @@ pub const base64_encoder = base64.Base64Encoder.init(...@@ -43,6 +45,13 @@ pub const base64_encoder = base64.Base64Encoder.init(
43 base64.standard_pad_char,45 base64.standard_pad_char,
44);46);
4547
48/// Whether or not async file system syscalls need a dedicated thread because the operating
49/// system does not support non-blocking I/O on the file system.
50pub const need_async_thread = std.io.is_async and switch (builtin.os) {
51 .windows, .other => false,
52 else => true,
53};
54
46/// TODO remove the allocator requirement from this API55/// TODO remove the allocator requirement from this API
47pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void {56pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void {
48 if (symLink(existing_path, new_path)) {57 if (symLink(existing_path, new_path)) {
...@@ -688,7 +697,11 @@ pub const Dir = struct {...@@ -688,7 +697,11 @@ pub const Dir = struct {
688 }697 }
689698
690 pub fn close(self: *Dir) void {699 pub fn close(self: *Dir) void {
691 os.close(self.fd);700 if (need_async_thread) {
701 std.event.Loop.instance.?.close(self.fd);
702 } else {
703 os.close(self.fd);
704 }
692 self.* = undefined;705 self.* = undefined;
693 }706 }
694707
...@@ -718,8 +731,11 @@ pub const Dir = struct {...@@ -718,8 +731,11 @@ pub const Dir = struct {
718 @as(u32, os.O_WRONLY)731 @as(u32, os.O_WRONLY)
719 else732 else
720 @as(u32, os.O_RDONLY);733 @as(u32, os.O_RDONLY);
721 const fd = try os.openatC(self.fd, sub_path, os_flags, 0);734 const fd = if (need_async_thread)
722 return File{ .handle = fd };735 try std.event.Loop.instance.?.openatZ(self.fd, sub_path, os_flags, 0)
736 else
737 try os.openatC(self.fd, sub_path, os_flags, 0);
738 return File{ .handle = fd, .io_mode = .blocking };
723 }739 }
724740
725 /// Same as `openFile` but Windows-only and the path parameter is741 /// Same as `openFile` but Windows-only and the path parameter is
...@@ -756,8 +772,11 @@ pub const Dir = struct {...@@ -756,8 +772,11 @@ pub const Dir = struct {
756 (if (flags.truncate) @as(u32, os.O_TRUNC) else 0) |772 (if (flags.truncate) @as(u32, os.O_TRUNC) else 0) |
757 (if (flags.read) @as(u32, os.O_RDWR) else os.O_WRONLY) |773 (if (flags.read) @as(u32, os.O_RDWR) else os.O_WRONLY) |
758 (if (flags.exclusive) @as(u32, os.O_EXCL) else 0);774 (if (flags.exclusive) @as(u32, os.O_EXCL) else 0);
759 const fd = try os.openatC(self.fd, sub_path_c, os_flags, flags.mode);775 const fd = if (need_async_thread)
760 return File{ .handle = fd };776 try std.event.Loop.instance.?.openatZ(self.fd, sub_path_c, os_flags, flags.mode)
777 else
778 try os.openatC(self.fd, sub_path_c, os_flags, flags.mode);
779 return File{ .handle = fd, .io_mode = .blocking };
761 }780 }
762781
763 /// Same as `createFile` but Windows-only and the path parameter is782 /// Same as `createFile` but Windows-only and the path parameter is
...@@ -919,7 +938,12 @@ pub const Dir = struct {...@@ -919,7 +938,12 @@ pub const Dir = struct {
919 }938 }
920939
921 fn openDirFlagsC(self: Dir, sub_path_c: [*:0]const u8, flags: u32) OpenError!Dir {940 fn openDirFlagsC(self: Dir, sub_path_c: [*:0]const u8, flags: u32) OpenError!Dir {
922 const fd = os.openatC(self.fd, sub_path_c, flags | os.O_DIRECTORY, 0) catch |err| switch (err) {941 const os_flags = flags | os.O_DIRECTORY;
942 const result = if (need_async_thread)
943 std.event.Loop.instance.?.openatZ(self.fd, sub_path_c, os_flags, 0)
944 else
945 os.openatC(self.fd, sub_path_c, os_flags, 0);
946 const fd = result catch |err| switch (err) {
923 error.FileTooBig => unreachable, // can't happen for directories947 error.FileTooBig => unreachable, // can't happen for directories
924 error.IsDir => unreachable, // we're providing O_DIRECTORY948 error.IsDir => unreachable, // we're providing O_DIRECTORY
925 error.NoSpaceLeft => unreachable, // not providing O_CREAT949 error.NoSpaceLeft => unreachable, // not providing O_CREAT
...@@ -1588,4 +1612,5 @@ test "" {...@@ -1588,4 +1612,5 @@ test "" {
1588 _ = @import("fs/path.zig");1612 _ = @import("fs/path.zig");
1589 _ = @import("fs/file.zig");1613 _ = @import("fs/file.zig");
1590 _ = @import("fs/get_app_data_dir.zig");1614 _ = @import("fs/get_app_data_dir.zig");
1615 _ = @import("fs/watch.zig");
1591}1616}
lib/std/fs/file.zig+77-74
...@@ -8,18 +8,29 @@ const assert = std.debug.assert;...@@ -8,18 +8,29 @@ const assert = std.debug.assert;
8const windows = os.windows;8const windows = os.windows;
9const Os = builtin.Os;9const Os = builtin.Os;
10const maxInt = std.math.maxInt;10const maxInt = std.math.maxInt;
11const need_async_thread = std.fs.need_async_thread;
1112
12pub const File = struct {13pub const File = struct {
13 /// The OS-specific file descriptor or file handle.14 /// The OS-specific file descriptor or file handle.
14 handle: os.fd_t,15 handle: os.fd_t,
1516
16 pub const Mode = switch (builtin.os) {17 /// On some systems, such as Linux, file system file descriptors are incapable of non-blocking I/O.
17 Os.windows => void,18 /// This forces us to perform asynchronous I/O on a dedicated thread, to achieve non-blocking
18 else => u32,19 /// file-system I/O. To do this, `File` must be aware of whether it is a file system file descriptor,
19 };20 /// or, more specifically, whether the I/O is blocking.
21 io_mode: io.Mode,
22
23 /// Even when std.io.mode is async, it is still sometimes desirable to perform blocking I/O, although
24 /// not by default. For example, when printing a stack trace to stderr.
25 async_block_allowed: @TypeOf(async_block_allowed_no) = async_block_allowed_no,
26
27 pub const async_block_allowed_yes = if (io.is_async) true else {};
28 pub const async_block_allowed_no = if (io.is_async) false else {};
29
30 pub const Mode = os.mode_t;
2031
21 pub const default_mode = switch (builtin.os) {32 pub const default_mode = switch (builtin.os) {
22 Os.windows => {},33 .windows => 0,
23 else => 0o666,34 else => 0o666,
24 };35 };
2536
...@@ -49,87 +60,27 @@ pub const File = struct {...@@ -49,87 +60,27 @@ pub const File = struct {
49 mode: Mode = default_mode,60 mode: Mode = default_mode,
50 };61 };
5162
52 /// Deprecated; call `std.fs.Dir.openFile` directly.
53 pub fn openRead(path: []const u8) OpenError!File {
54 return std.fs.cwd().openFile(path, .{});
55 }
56
57 /// Deprecated; call `std.fs.Dir.openFileC` directly.
58 pub fn openReadC(path_c: [*:0]const u8) OpenError!File {
59 return std.fs.cwd().openFileC(path_c, .{});
60 }
61
62 /// Deprecated; call `std.fs.Dir.openFileW` directly.
63 pub fn openReadW(path_w: [*:0]const u16) OpenError!File {
64 return std.fs.cwd().openFileW(path_w, .{});
65 }
66
67 /// Deprecated; call `std.fs.Dir.createFile` directly.
68 pub fn openWrite(path: []const u8) OpenError!File {
69 return std.fs.cwd().createFile(path, .{});
70 }
71
72 /// Deprecated; call `std.fs.Dir.createFile` directly.
73 pub fn openWriteMode(path: []const u8, file_mode: Mode) OpenError!File {
74 return std.fs.cwd().createFile(path, .{ .mode = file_mode });
75 }
76
77 /// Deprecated; call `std.fs.Dir.createFileC` directly.
78 pub fn openWriteModeC(path_c: [*:0]const u8, file_mode: Mode) OpenError!File {
79 return std.fs.cwd().createFileC(path_c, .{ .mode = file_mode });
80 }
81
82 /// Deprecated; call `std.fs.Dir.createFileW` directly.
83 pub fn openWriteModeW(path_w: [*:0]const u16, file_mode: Mode) OpenError!File {
84 return std.fs.cwd().createFileW(path_w, .{ .mode = file_mode });
85 }
86
87 /// Deprecated; call `std.fs.Dir.createFile` directly.
88 pub fn openWriteNoClobber(path: []const u8, file_mode: Mode) OpenError!File {
89 return std.fs.cwd().createFile(path, .{
90 .mode = file_mode,
91 .exclusive = true,
92 });
93 }
94
95 /// Deprecated; call `std.fs.Dir.createFileC` directly.
96 pub fn openWriteNoClobberC(path_c: [*:0]const u8, file_mode: Mode) OpenError!File {
97 return std.fs.cwd().createFileC(path_c, .{
98 .mode = file_mode,
99 .exclusive = true,
100 });
101 }
102
103 /// Deprecated; call `std.fs.Dir.createFileW` directly.
104 pub fn openWriteNoClobberW(path_w: [*:0]const u16, file_mode: Mode) OpenError!File {
105 return std.fs.cwd().createFileW(path_w, .{
106 .mode = file_mode,
107 .exclusive = true,
108 });
109 }
110
111 pub fn openHandle(handle: os.fd_t) File {
112 return File{ .handle = handle };
113 }
114
115 /// Test for the existence of `path`.63 /// Test for the existence of `path`.
116 /// `path` is UTF8-encoded.64 /// `path` is UTF8-encoded.
117 /// In general it is recommended to avoid this function. For example,65 /// In general it is recommended to avoid this function. For example,
118 /// instead of testing if a file exists and then opening it, just66 /// instead of testing if a file exists and then opening it, just
119 /// open it and handle the error for file not found.67 /// open it and handle the error for file not found.
120 /// TODO: deprecate this and move it to `std.fs.Dir`.68 /// TODO: deprecate this and move it to `std.fs.Dir`.
69 /// TODO: integrate with async I/O
121 pub fn access(path: []const u8) !void {70 pub fn access(path: []const u8) !void {
122 return os.access(path, os.F_OK);71 return os.access(path, os.F_OK);
123 }72 }
12473
125 /// Same as `access` except the parameter is null-terminated.74 /// Same as `access` except the parameter is null-terminated.
126 /// TODO: deprecate this and move it to `std.fs.Dir`.75 /// TODO: deprecate this and move it to `std.fs.Dir`.
76 /// TODO: integrate with async I/O
127 pub fn accessC(path: [*:0]const u8) !void {77 pub fn accessC(path: [*:0]const u8) !void {
128 return os.accessC(path, os.F_OK);78 return os.accessC(path, os.F_OK);
129 }79 }
13080
131 /// Same as `access` except the parameter is null-terminated UTF16LE-encoded.81 /// Same as `access` except the parameter is null-terminated UTF16LE-encoded.
132 /// TODO: deprecate this and move it to `std.fs.Dir`.82 /// TODO: deprecate this and move it to `std.fs.Dir`.
83 /// TODO: integrate with async I/O
133 pub fn accessW(path: [*:0]const u16) !void {84 pub fn accessW(path: [*:0]const u16) !void {
134 return os.accessW(path, os.F_OK);85 return os.accessW(path, os.F_OK);
135 }86 }
...@@ -137,7 +88,11 @@ pub const File = struct {...@@ -137,7 +88,11 @@ pub const File = struct {
137 /// Upon success, the stream is in an uninitialized state. To continue using it,88 /// Upon success, the stream is in an uninitialized state. To continue using it,
138 /// you must use the open() function.89 /// you must use the open() function.
139 pub fn close(self: File) void {90 pub fn close(self: File) void {
140 return os.close(self.handle);91 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
92 std.event.Loop.instance.?.close(self.handle);
93 } else {
94 return os.close(self.handle);
95 }
141 }96 }
14297
143 /// Test whether the file refers to a terminal.98 /// Test whether the file refers to a terminal.
...@@ -167,26 +122,31 @@ pub const File = struct {...@@ -167,26 +122,31 @@ pub const File = struct {
167 pub const SeekError = os.SeekError;122 pub const SeekError = os.SeekError;
168123
169 /// Repositions read/write file offset relative to the current offset.124 /// Repositions read/write file offset relative to the current offset.
125 /// TODO: integrate with async I/O
170 pub fn seekBy(self: File, offset: i64) SeekError!void {126 pub fn seekBy(self: File, offset: i64) SeekError!void {
171 return os.lseek_CUR(self.handle, offset);127 return os.lseek_CUR(self.handle, offset);
172 }128 }
173129
174 /// Repositions read/write file offset relative to the end.130 /// Repositions read/write file offset relative to the end.
131 /// TODO: integrate with async I/O
175 pub fn seekFromEnd(self: File, offset: i64) SeekError!void {132 pub fn seekFromEnd(self: File, offset: i64) SeekError!void {
176 return os.lseek_END(self.handle, offset);133 return os.lseek_END(self.handle, offset);
177 }134 }
178135
179 /// Repositions read/write file offset relative to the beginning.136 /// Repositions read/write file offset relative to the beginning.
137 /// TODO: integrate with async I/O
180 pub fn seekTo(self: File, offset: u64) SeekError!void {138 pub fn seekTo(self: File, offset: u64) SeekError!void {
181 return os.lseek_SET(self.handle, offset);139 return os.lseek_SET(self.handle, offset);
182 }140 }
183141
184 pub const GetPosError = os.SeekError || os.FStatError;142 pub const GetPosError = os.SeekError || os.FStatError;
185143
144 /// TODO: integrate with async I/O
186 pub fn getPos(self: File) GetPosError!u64 {145 pub fn getPos(self: File) GetPosError!u64 {
187 return os.lseek_CUR_get(self.handle);146 return os.lseek_CUR_get(self.handle);
188 }147 }
189148
149 /// TODO: integrate with async I/O
190 pub fn getEndPos(self: File) GetPosError!u64 {150 pub fn getEndPos(self: File) GetPosError!u64 {
191 if (builtin.os == .windows) {151 if (builtin.os == .windows) {
192 return windows.GetFileSizeEx(self.handle);152 return windows.GetFileSizeEx(self.handle);
...@@ -196,6 +156,7 @@ pub const File = struct {...@@ -196,6 +156,7 @@ pub const File = struct {
196156
197 pub const ModeError = os.FStatError;157 pub const ModeError = os.FStatError;
198158
159 /// TODO: integrate with async I/O
199 pub fn mode(self: File) ModeError!Mode {160 pub fn mode(self: File) ModeError!Mode {
200 if (builtin.os == .windows) {161 if (builtin.os == .windows) {
201 return {};162 return {};
...@@ -219,6 +180,7 @@ pub const File = struct {...@@ -219,6 +180,7 @@ pub const File = struct {
219180
220 pub const StatError = os.FStatError;181 pub const StatError = os.FStatError;
221182
183 /// TODO: integrate with async I/O
222 pub fn stat(self: File) StatError!Stat {184 pub fn stat(self: File) StatError!Stat {
223 if (builtin.os == .windows) {185 if (builtin.os == .windows) {
224 var io_status_block: windows.IO_STATUS_BLOCK = undefined;186 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
...@@ -259,6 +221,7 @@ pub const File = struct {...@@ -259,6 +221,7 @@ pub const File = struct {
259 /// and therefore this function cannot guarantee any precision will be stored.221 /// and therefore this function cannot guarantee any precision will be stored.
260 /// Further, the maximum value is limited by the system ABI. When a value is provided222 /// Further, the maximum value is limited by the system ABI. When a value is provided
261 /// that exceeds this range, the value is clamped to the maximum.223 /// that exceeds this range, the value is clamped to the maximum.
224 /// TODO: integrate with async I/O
262 pub fn updateTimes(225 pub fn updateTimes(
263 self: File,226 self: File,
264 /// access timestamp in nanoseconds227 /// access timestamp in nanoseconds
...@@ -287,21 +250,61 @@ pub const File = struct {...@@ -287,21 +250,61 @@ pub const File = struct {
287 pub const ReadError = os.ReadError;250 pub const ReadError = os.ReadError;
288251
289 pub fn read(self: File, buffer: []u8) ReadError!usize {252 pub fn read(self: File, buffer: []u8) ReadError!usize {
253 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
254 return std.event.Loop.instance.?.read(self.handle, buffer);
255 }
290 return os.read(self.handle, buffer);256 return os.read(self.handle, buffer);
291 }257 }
292258
259 pub fn pread(self: File, buffer: []u8, offset: u64) ReadError!usize {
260 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
261 return std.event.Loop.instance.?.pread(self.handle, buffer);
262 }
263 return os.pread(self.handle, buffer, offset);
264 }
265
266 pub fn readv(self: File, iovecs: []const os.iovec) ReadError!usize {
267 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
268 return std.event.Loop.instance.?.readv(self.handle, iovecs);
269 }
270 return os.readv(self.handle, iovecs);
271 }
272
273 pub fn preadv(self: File, iovecs: []const os.iovec, offset: u64) ReadError!usize {
274 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
275 return std.event.Loop.instance.?.preadv(self.handle, iovecs, offset);
276 }
277 return os.preadv(self.handle, iovecs, offset);
278 }
279
293 pub const WriteError = os.WriteError;280 pub const WriteError = os.WriteError;
294281
295 pub fn write(self: File, bytes: []const u8) WriteError!void {282 pub fn write(self: File, bytes: []const u8) WriteError!void {
283 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
284 return std.event.Loop.instance.?.write(self.handle, bytes);
285 }
296 return os.write(self.handle, bytes);286 return os.write(self.handle, bytes);
297 }287 }
298288
299 pub fn writev_iovec(self: File, iovecs: []const os.iovec_const) WriteError!void {289 pub fn pwrite(self: File, bytes: []const u8, offset: u64) WriteError!void {
300 if (std.event.Loop.instance) |loop| {290 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
301 return std.event.fs.writevPosix(loop, self.handle, iovecs);291 return std.event.Loop.instance.?.pwrite(self.handle, bytes, offset);
302 } else {292 }
303 return os.writev(self.handle, iovecs);293 return os.pwrite(self.handle, bytes, offset);
294 }
295
296 pub fn writev(self: File, iovecs: []const os.iovec_const) WriteError!void {
297 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
298 return std.event.Loop.instance.?.writev(self.handle, iovecs);
299 }
300 return os.writev(self.handle, iovecs);
301 }
302
303 pub fn pwritev(self: File, iovecs: []const os.iovec_const, offset: usize) WriteError!void {
304 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
305 return std.event.Loop.instance.?.pwritev(self.handle, iovecs);
304 }306 }
307 return os.pwritev(self.handle, iovecs);
305 }308 }
306309
307 pub fn inStream(file: File) InStream {310 pub fn inStream(file: File) InStream {
lib/std/fs/watch.zig created+673
...@@ -0,0 +1,673 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const event = std.event;
4const assert = std.debug.assert;
5const testing = std.testing;
6const os = std.os;
7const mem = std.mem;
8const windows = os.windows;
9const Loop = event.Loop;
10const fd_t = os.fd_t;
11const File = std.fs.File;
12const Allocator = mem.Allocator;
13
14const WatchEventId = enum {
15 CloseWrite,
16 Delete,
17};
18
19fn eqlString(a: []const u16, b: []const u16) bool {
20 if (a.len != b.len) return false;
21 if (a.ptr == b.ptr) return true;
22 return mem.compare(u16, a, b) == .Equal;
23}
24
25fn hashString(s: []const u16) u32 {
26 return @truncate(u32, std.hash.Wyhash.hash(0, @sliceToBytes(s)));
27}
28
29const WatchEventError = error{
30 UserResourceLimitReached,
31 SystemResources,
32 AccessDenied,
33 Unexpected, // TODO remove this possibility
34};
35
36pub fn Watch(comptime V: type) type {
37 return struct {
38 channel: *event.Channel(Event.Error!Event),
39 os_data: OsData,
40 allocator: *Allocator,
41
42 const OsData = switch (builtin.os) {
43 // TODO https://github.com/ziglang/zig/issues/3778
44 .macosx, .freebsd, .netbsd, .dragonfly => KqOsData,
45 .linux => LinuxOsData,
46 .windows => WindowsOsData,
47
48 else => @compileError("Unsupported OS"),
49 };
50
51 const KqOsData = struct {
52 file_table: FileTable,
53 table_lock: event.Lock,
54
55 const FileTable = std.StringHashMap(*Put);
56 const Put = struct {
57 putter_frame: @Frame(kqPutEvents),
58 cancelled: bool = false,
59 value: V,
60 };
61 };
62
63 const WindowsOsData = struct {
64 table_lock: event.Lock,
65 dir_table: DirTable,
66 all_putters: std.atomic.Queue(Put),
67 ref_count: std.atomic.Int(usize),
68
69 const Put = struct {
70 putter: anyframe,
71 cancelled: bool = false,
72 };
73
74 const DirTable = std.StringHashMap(*Dir);
75 const FileTable = std.HashMap([]const u16, V, hashString, eqlString);
76
77 const Dir = struct {
78 putter_frame: @Frame(windowsDirReader),
79 file_table: FileTable,
80 table_lock: event.Lock,
81 };
82 };
83
84 const LinuxOsData = struct {
85 putter_frame: @Frame(linuxEventPutter),
86 inotify_fd: i32,
87 wd_table: WdTable,
88 table_lock: event.Lock,
89 cancelled: bool = false,
90
91 const WdTable = std.AutoHashMap(i32, Dir);
92 const FileTable = std.StringHashMap(V);
93
94 const Dir = struct {
95 dirname: []const u8,
96 file_table: FileTable,
97 };
98 };
99
100 const Self = @This();
101
102 pub const Event = struct {
103 id: Id,
104 data: V,
105
106 pub const Id = WatchEventId;
107 pub const Error = WatchEventError;
108 };
109
110 pub fn init(allocator: *Allocator, event_buf_count: usize) !*Self {
111 const channel = try allocator.create(event.Channel(Event.Error!Event));
112 errdefer allocator.destroy(channel);
113 var buf = try allocator.alloc(Event.Error!Event, event_buf_count);
114 errdefer allocator.free(buf);
115 channel.init(buf);
116 errdefer channel.deinit();
117
118 const self = try allocator.create(Self);
119 errdefer allocator.destroy(self);
120
121 switch (builtin.os) {
122 .linux => {
123 const inotify_fd = try os.inotify_init1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC);
124 errdefer os.close(inotify_fd);
125
126 self.* = Self{
127 .allocator = allocator,
128 .channel = channel,
129 .os_data = OsData{
130 .putter_frame = undefined,
131 .inotify_fd = inotify_fd,
132 .wd_table = OsData.WdTable.init(allocator),
133 .table_lock = event.Lock.init(),
134 },
135 };
136
137 self.os_data.putter_frame = async self.linuxEventPutter();
138 return self;
139 },
140
141 .windows => {
142 self.* = Self{
143 .allocator = allocator,
144 .channel = channel,
145 .os_data = OsData{
146 .table_lock = event.Lock.init(),
147 .dir_table = OsData.DirTable.init(allocator),
148 .ref_count = std.atomic.Int(usize).init(1),
149 .all_putters = std.atomic.Queue(anyframe).init(),
150 },
151 };
152 return self;
153 },
154
155 .macosx, .freebsd, .netbsd, .dragonfly => {
156 self.* = Self{
157 .allocator = allocator,
158 .channel = channel,
159 .os_data = OsData{
160 .table_lock = event.Lock.init(),
161 .file_table = OsData.FileTable.init(allocator),
162 },
163 };
164 return self;
165 },
166 else => @compileError("Unsupported OS"),
167 }
168 }
169
170 /// All addFile calls and removeFile calls must have completed.
171 pub fn deinit(self: *Self) void {
172 switch (builtin.os) {
173 .macosx, .freebsd, .netbsd, .dragonfly => {
174 // TODO we need to cancel the frames before destroying the lock
175 self.os_data.table_lock.deinit();
176 var it = self.os_data.file_table.iterator();
177 while (it.next()) |entry| {
178 entry.cancelled = true;
179 await entry.value.putter;
180 self.allocator.free(entry.key);
181 self.allocator.free(entry.value);
182 }
183 self.channel.deinit();
184 self.allocator.destroy(self.channel.buffer_nodes);
185 self.allocator.destroy(self);
186 },
187 .linux => {
188 self.os_data.cancelled = true;
189 await self.os_data.putter_frame;
190 self.allocator.destroy(self);
191 },
192 .windows => {
193 while (self.os_data.all_putters.get()) |putter_node| {
194 putter_node.cancelled = true;
195 await putter_node.frame;
196 }
197 self.deref();
198 },
199 else => @compileError("Unsupported OS"),
200 }
201 }
202
203 fn ref(self: *Self) void {
204 _ = self.os_data.ref_count.incr();
205 }
206
207 fn deref(self: *Self) void {
208 if (self.os_data.ref_count.decr() == 1) {
209 self.os_data.table_lock.deinit();
210 var it = self.os_data.dir_table.iterator();
211 while (it.next()) |entry| {
212 self.allocator.free(entry.key);
213 self.allocator.destroy(entry.value);
214 }
215 self.os_data.dir_table.deinit();
216 self.channel.deinit();
217 self.allocator.destroy(self.channel.buffer_nodes);
218 self.allocator.destroy(self);
219 }
220 }
221
222 pub fn addFile(self: *Self, file_path: []const u8, value: V) !?V {
223 switch (builtin.os) {
224 .macosx, .freebsd, .netbsd, .dragonfly => return addFileKEvent(self, file_path, value),
225 .linux => return addFileLinux(self, file_path, value),
226 .windows => return addFileWindows(self, file_path, value),
227 else => @compileError("Unsupported OS"),
228 }
229 }
230
231 fn addFileKEvent(self: *Self, file_path: []const u8, value: V) !?V {
232 const resolved_path = try std.fs.path.resolve(self.allocator, [_][]const u8{file_path});
233 var resolved_path_consumed = false;
234 defer if (!resolved_path_consumed) self.allocator.free(resolved_path);
235
236 var close_op = try CloseOperation.start(self.allocator);
237 var close_op_consumed = false;
238 defer if (!close_op_consumed) close_op.finish();
239
240 const flags = if (comptime std.Target.current.isDarwin()) os.O_SYMLINK | os.O_EVTONLY else 0;
241 const mode = 0;
242 const fd = try openPosix(self.allocator, resolved_path, flags, mode);
243 close_op.setHandle(fd);
244
245 var put = try self.allocator.create(OsData.Put);
246 errdefer self.allocator.destroy(put);
247 put.* = OsData.Put{
248 .value = value,
249 .putter_frame = undefined,
250 };
251 put.putter_frame = async self.kqPutEvents(close_op, put);
252 close_op_consumed = true;
253 errdefer {
254 put.cancelled = true;
255 await put.putter_frame;
256 }
257
258 const result = blk: {
259 const held = self.os_data.table_lock.acquire();
260 defer held.release();
261
262 const gop = try self.os_data.file_table.getOrPut(resolved_path);
263 if (gop.found_existing) {
264 const prev_value = gop.kv.value.value;
265 await gop.kv.value.putter_frame;
266 gop.kv.value = put;
267 break :blk prev_value;
268 } else {
269 resolved_path_consumed = true;
270 gop.kv.value = put;
271 break :blk null;
272 }
273 };
274
275 return result;
276 }
277
278 fn kqPutEvents(self: *Self, close_op: *CloseOperation, put: *OsData.Put) void {
279 global_event_loop.beginOneEvent();
280
281 defer {
282 close_op.finish();
283 global_event_loop.finishOneEvent();
284 }
285
286 while (!put.cancelled) {
287 if (global_event_loop.bsdWaitKev(
288 @intCast(usize, close_op.getHandle()),
289 os.EVFILT_VNODE,
290 os.NOTE_WRITE | os.NOTE_DELETE,
291 )) |kev| {
292 // TODO handle EV_ERROR
293 if (kev.fflags & os.NOTE_DELETE != 0) {
294 self.channel.put(Self.Event{
295 .id = Event.Id.Delete,
296 .data = put.value,
297 });
298 } else if (kev.fflags & os.NOTE_WRITE != 0) {
299 self.channel.put(Self.Event{
300 .id = Event.Id.CloseWrite,
301 .data = put.value,
302 });
303 }
304 } else |err| switch (err) {
305 error.EventNotFound => unreachable,
306 error.ProcessNotFound => unreachable,
307 error.Overflow => unreachable,
308 error.AccessDenied, error.SystemResources => |casted_err| {
309 self.channel.put(casted_err);
310 },
311 }
312 }
313 }
314
315 fn addFileLinux(self: *Self, file_path: []const u8, value: V) !?V {
316 const dirname = std.fs.path.dirname(file_path) orelse ".";
317 const dirname_with_null = try std.cstr.addNullByte(self.allocator, dirname);
318 var dirname_with_null_consumed = false;
319 defer if (!dirname_with_null_consumed) self.channel.free(dirname_with_null);
320
321 const basename = std.fs.path.basename(file_path);
322 const basename_with_null = try std.cstr.addNullByte(self.allocator, basename);
323 var basename_with_null_consumed = false;
324 defer if (!basename_with_null_consumed) self.allocator.free(basename_with_null);
325
326 const wd = try os.inotify_add_watchC(
327 self.os_data.inotify_fd,
328 dirname_with_null.ptr,
329 os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_EXCL_UNLINK,
330 );
331 // wd is either a newly created watch or an existing one.
332
333 const held = self.os_data.table_lock.acquire();
334 defer held.release();
335
336 const gop = try self.os_data.wd_table.getOrPut(wd);
337 if (!gop.found_existing) {
338 gop.kv.value = OsData.Dir{
339 .dirname = dirname_with_null,
340 .file_table = OsData.FileTable.init(self.allocator),
341 };
342 dirname_with_null_consumed = true;
343 }
344 const dir = &gop.kv.value;
345
346 const file_table_gop = try dir.file_table.getOrPut(basename_with_null);
347 if (file_table_gop.found_existing) {
348 const prev_value = file_table_gop.kv.value;
349 file_table_gop.kv.value = value;
350 return prev_value;
351 } else {
352 file_table_gop.kv.value = value;
353 basename_with_null_consumed = true;
354 return null;
355 }
356 }
357
358 fn addFileWindows(self: *Self, file_path: []const u8, value: V) !?V {
359 // TODO we might need to convert dirname and basename to canonical file paths ("short"?)
360 const dirname = try std.mem.dupe(self.allocator, u8, std.fs.path.dirname(file_path) orelse ".");
361 var dirname_consumed = false;
362 defer if (!dirname_consumed) self.allocator.free(dirname);
363
364 const dirname_utf16le = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, dirname);
365 defer self.allocator.free(dirname_utf16le);
366
367 // TODO https://github.com/ziglang/zig/issues/265
368 const basename = std.fs.path.basename(file_path);
369 const basename_utf16le_null = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, basename);
370 var basename_utf16le_null_consumed = false;
371 defer if (!basename_utf16le_null_consumed) self.allocator.free(basename_utf16le_null);
372 const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1];
373
374 const dir_handle = try windows.CreateFileW(
375 dirname_utf16le.ptr,
376 windows.FILE_LIST_DIRECTORY,
377 windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE | windows.FILE_SHARE_WRITE,
378 null,
379 windows.OPEN_EXISTING,
380 windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OVERLAPPED,
381 null,
382 );
383 var dir_handle_consumed = false;
384 defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle);
385
386 const held = self.os_data.table_lock.acquire();
387 defer held.release();
388
389 const gop = try self.os_data.dir_table.getOrPut(dirname);
390 if (gop.found_existing) {
391 const dir = gop.kv.value;
392 const held_dir_lock = dir.table_lock.acquire();
393 defer held_dir_lock.release();
394
395 const file_gop = try dir.file_table.getOrPut(basename_utf16le_no_null);
396 if (file_gop.found_existing) {
397 const prev_value = file_gop.kv.value;
398 file_gop.kv.value = value;
399 return prev_value;
400 } else {
401 file_gop.kv.value = value;
402 basename_utf16le_null_consumed = true;
403 return null;
404 }
405 } else {
406 errdefer _ = self.os_data.dir_table.remove(dirname);
407 const dir = try self.allocator.create(OsData.Dir);
408 errdefer self.allocator.destroy(dir);
409
410 dir.* = OsData.Dir{
411 .file_table = OsData.FileTable.init(self.allocator),
412 .table_lock = event.Lock.init(),
413 .putter_frame = undefined,
414 };
415 gop.kv.value = dir;
416 assert((try dir.file_table.put(basename_utf16le_no_null, value)) == null);
417 basename_utf16le_null_consumed = true;
418
419 dir.putter_frame = async self.windowsDirReader(dir_handle, dir);
420 dir_handle_consumed = true;
421
422 dirname_consumed = true;
423
424 return null;
425 }
426 }
427
428 fn windowsDirReader(self: *Self, dir_handle: windows.HANDLE, dir: *OsData.Dir) void {
429 self.ref();
430 defer self.deref();
431
432 defer os.close(dir_handle);
433
434 var putter_node = std.atomic.Queue(anyframe).Node{
435 .data = .{ .putter = @frame() },
436 .prev = null,
437 .next = null,
438 };
439 self.os_data.all_putters.put(&putter_node);
440 defer _ = self.os_data.all_putters.remove(&putter_node);
441
442 var resume_node = Loop.ResumeNode.Basic{
443 .base = Loop.ResumeNode{
444 .id = Loop.ResumeNode.Id.Basic,
445 .handle = @frame(),
446 .overlapped = windows.OVERLAPPED{
447 .Internal = 0,
448 .InternalHigh = 0,
449 .Offset = 0,
450 .OffsetHigh = 0,
451 .hEvent = null,
452 },
453 },
454 };
455 var event_buf: [4096]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined;
456
457 // TODO handle this error not in the channel but in the setup
458 _ = windows.CreateIoCompletionPort(
459 dir_handle,
460 global_event_loop.os_data.io_port,
461 undefined,
462 undefined,
463 ) catch |err| {
464 self.channel.put(err);
465 return;
466 };
467
468 while (!putter_node.data.cancelled) {
469 {
470 // TODO only 1 beginOneEvent for the whole function
471 global_event_loop.beginOneEvent();
472 errdefer global_event_loop.finishOneEvent();
473 errdefer {
474 _ = windows.kernel32.CancelIoEx(dir_handle, &resume_node.base.overlapped);
475 }
476 suspend {
477 _ = windows.kernel32.ReadDirectoryChangesW(
478 dir_handle,
479 &event_buf,
480 @intCast(windows.DWORD, event_buf.len),
481 windows.FALSE, // watch subtree
482 windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME |
483 windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE |
484 windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS |
485 windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY,
486 null, // number of bytes transferred (unused for async)
487 &resume_node.base.overlapped,
488 null, // completion routine - unused because we use IOCP
489 );
490 }
491 }
492 var bytes_transferred: windows.DWORD = undefined;
493 if (windows.kernel32.GetOverlappedResult(dir_handle, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) {
494 const err = switch (windows.kernel32.GetLastError()) {
495 else => |err| windows.unexpectedError(err),
496 };
497 self.channel.put(err);
498 } else {
499 // can't use @bytesToSlice because of the special variable length name field
500 var ptr = event_buf[0..].ptr;
501 const end_ptr = ptr + bytes_transferred;
502 var ev: *windows.FILE_NOTIFY_INFORMATION = undefined;
503 while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += ev.NextEntryOffset) {
504 ev = @ptrCast(*windows.FILE_NOTIFY_INFORMATION, ptr);
505 const emit = switch (ev.Action) {
506 windows.FILE_ACTION_REMOVED => WatchEventId.Delete,
507 windows.FILE_ACTION_MODIFIED => WatchEventId.CloseWrite,
508 else => null,
509 };
510 if (emit) |id| {
511 const basename_utf16le = ([*]u16)(&ev.FileName)[0 .. ev.FileNameLength / 2];
512 const user_value = blk: {
513 const held = dir.table_lock.acquire();
514 defer held.release();
515
516 if (dir.file_table.get(basename_utf16le)) |entry| {
517 break :blk entry.value;
518 } else {
519 break :blk null;
520 }
521 };
522 if (user_value) |v| {
523 self.channel.put(Event{
524 .id = id,
525 .data = v,
526 });
527 }
528 }
529 if (ev.NextEntryOffset == 0) break;
530 }
531 }
532 }
533 }
534
535 pub fn removeFile(self: *Self, file_path: []const u8) ?V {
536 @panic("TODO");
537 }
538
539 fn linuxEventPutter(self: *Self) void {
540 global_event_loop.beginOneEvent();
541
542 defer {
543 self.os_data.table_lock.deinit();
544 var wd_it = self.os_data.wd_table.iterator();
545 while (wd_it.next()) |wd_entry| {
546 var file_it = wd_entry.value.file_table.iterator();
547 while (file_it.next()) |file_entry| {
548 self.allocator.free(file_entry.key);
549 }
550 self.allocator.free(wd_entry.value.dirname);
551 wd_entry.value.file_table.deinit();
552 }
553 self.os_data.wd_table.deinit();
554 global_event_loop.finishOneEvent();
555 os.close(self.os_data.inotify_fd);
556 self.channel.deinit();
557 self.allocator.free(self.channel.buffer_nodes);
558 }
559
560 var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined;
561
562 while (!self.os_data.cancelled) {
563 const rc = os.linux.read(self.os_data.inotify_fd, &event_buf, event_buf.len);
564 const errno = os.linux.getErrno(rc);
565 switch (errno) {
566 0 => {
567 // can't use @bytesToSlice because of the special variable length name field
568 var ptr = event_buf[0..].ptr;
569 const end_ptr = ptr + event_buf.len;
570 var ev: *os.linux.inotify_event = undefined;
571 while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) {
572 ev = @ptrCast(*os.linux.inotify_event, ptr);
573 if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) {
574 const basename_ptr = ptr + @sizeOf(os.linux.inotify_event);
575 // `ev.len` counts all bytes in `ev.name` including terminating null byte.
576 const basename_with_null = basename_ptr[0..ev.len];
577 const user_value = blk: {
578 const held = self.os_data.table_lock.acquire();
579 defer held.release();
580
581 const dir = &self.os_data.wd_table.get(ev.wd).?.value;
582 if (dir.file_table.get(basename_with_null)) |entry| {
583 break :blk entry.value;
584 } else {
585 break :blk null;
586 }
587 };
588 if (user_value) |v| {
589 self.channel.put(Event{
590 .id = WatchEventId.CloseWrite,
591 .data = v,
592 });
593 }
594 }
595
596 ptr = @alignCast(@alignOf(os.linux.inotify_event), ptr + @sizeOf(os.linux.inotify_event) + ev.len);
597 }
598 },
599 os.linux.EINTR => continue,
600 os.linux.EINVAL => unreachable,
601 os.linux.EFAULT => unreachable,
602 os.linux.EAGAIN => {
603 global_event_loop.linuxWaitFd(self.os_data.inotify_fd, os.linux.EPOLLET | os.linux.EPOLLIN | os.EPOLLONESHOT);
604 },
605 else => unreachable,
606 }
607 }
608 }
609 };
610}
611
612const test_tmp_dir = "std_event_fs_test";
613
614test "write a file, watch it, write it again" {
615 // TODO provide a way to run tests in evented I/O mode
616 if (!std.io.is_async) return error.SkipZigTest;
617
618 const allocator = std.heap.page_allocator;
619
620 // TODO move this into event loop too
621 try os.makePath(allocator, test_tmp_dir);
622 defer os.deleteTree(test_tmp_dir) catch {};
623
624 return testFsWatch(&allocator);
625}
626
627fn testFsWatch(allocator: *Allocator) !void {
628 const file_path = try std.fs.path.join(allocator, [_][]const u8{ test_tmp_dir, "file.txt" });
629 defer allocator.free(file_path);
630
631 const contents =
632 \\line 1
633 \\line 2
634 ;
635 const line2_offset = 7;
636
637 // first just write then read the file
638 try writeFile(allocator, file_path, contents);
639
640 const read_contents = try readFile(allocator, file_path, 1024 * 1024);
641 testing.expectEqualSlices(u8, contents, read_contents);
642
643 // now watch the file
644 var watch = try Watch(void).init(allocator, 0);
645 defer watch.deinit();
646
647 testing.expect((try watch.addFile(file_path, {})) == null);
648
649 const ev = watch.channel.get();
650 var ev_consumed = false;
651 defer if (!ev_consumed) await ev;
652
653 // overwrite line 2
654 const fd = try await openReadWrite(file_path, File.default_mode);
655 {
656 defer os.close(fd);
657
658 try pwritev(allocator, fd, []const []const u8{"lorem ipsum"}, line2_offset);
659 }
660
661 ev_consumed = true;
662 switch ((try await ev).id) {
663 WatchEventId.CloseWrite => {},
664 WatchEventId.Delete => @panic("wrong event"),
665 }
666 const contents_updated = try readFile(allocator, file_path, 1024 * 1024);
667 testing.expectEqualSlices(u8,
668 \\line 1
669 \\lorem ipsum
670 , contents_updated);
671
672 // TODO test deleting the file and then re-adding it. we should get events for both
673}
lib/std/io.zig+13-3
...@@ -47,7 +47,10 @@ fn getStdOutHandle() os.fd_t {...@@ -47,7 +47,10 @@ fn getStdOutHandle() os.fd_t {
47}47}
4848
49pub fn getStdOut() File {49pub fn getStdOut() File {
50 return File.openHandle(getStdOutHandle());50 return File{
51 .handle = getStdOutHandle(),
52 .io_mode = .blocking,
53 };
51}54}
5255
53fn getStdErrHandle() os.fd_t {56fn getStdErrHandle() os.fd_t {
...@@ -63,7 +66,11 @@ fn getStdErrHandle() os.fd_t {...@@ -63,7 +66,11 @@ fn getStdErrHandle() os.fd_t {
63}66}
6467
65pub fn getStdErr() File {68pub fn getStdErr() File {
66 return File.openHandle(getStdErrHandle());69 return File{
70 .handle = getStdErrHandle(),
71 .io_mode = .blocking,
72 .async_block_allowed = File.async_block_allowed_yes,
73 };
67}74}
6875
69fn getStdInHandle() os.fd_t {76fn getStdInHandle() os.fd_t {
...@@ -79,7 +86,10 @@ fn getStdInHandle() os.fd_t {...@@ -79,7 +86,10 @@ fn getStdInHandle() os.fd_t {
79}86}
8087
81pub fn getStdIn() File {88pub fn getStdIn() File {
82 return File.openHandle(getStdInHandle());89 return File{
90 .handle = getStdInHandle(),
91 .io_mode = .blocking,
92 };
83}93}
8494
85pub const SeekableStream = @import("io/seekable_stream.zig").SeekableStream;95pub const SeekableStream = @import("io/seekable_stream.zig").SeekableStream;
lib/std/io/out_stream.zig+10-14
...@@ -9,14 +9,11 @@ pub const stack_size: usize = if (@hasDecl(root, "stack_size_std_io_OutStream"))...@@ -9,14 +9,11 @@ pub const stack_size: usize = if (@hasDecl(root, "stack_size_std_io_OutStream"))
9else9else
10 default_stack_size;10 default_stack_size;
1111
12/// TODO this is not integrated with evented I/O yet.
13/// https://github.com/ziglang/zig/issues/3557
14pub fn OutStream(comptime WriteError: type) type {12pub fn OutStream(comptime WriteError: type) type {
15 return struct {13 return struct {
16 const Self = @This();14 const Self = @This();
17 pub const Error = WriteError;15 pub const Error = WriteError;
18 // TODO https://github.com/ziglang/zig/issues/355716 pub const WriteFn = if (std.io.is_async)
19 pub const WriteFn = if (std.io.is_async and false)
20 async fn (self: *Self, bytes: []const u8) Error!void17 async fn (self: *Self, bytes: []const u8) Error!void
21 else18 else
22 fn (self: *Self, bytes: []const u8) Error!void;19 fn (self: *Self, bytes: []const u8) Error!void;
...@@ -24,8 +21,7 @@ pub fn OutStream(comptime WriteError: type) type {...@@ -24,8 +21,7 @@ pub fn OutStream(comptime WriteError: type) type {
24 writeFn: WriteFn,21 writeFn: WriteFn,
2522
26 pub fn write(self: *Self, bytes: []const u8) Error!void {23 pub fn write(self: *Self, bytes: []const u8) Error!void {
27 // TODO https://github.com/ziglang/zig/issues/355724 if (std.io.is_async) {
28 if (std.io.is_async and false) {
29 // Let's not be writing 0xaa in safe modes for upwards of 4 MiB for every stream write.25 // Let's not be writing 0xaa in safe modes for upwards of 4 MiB for every stream write.
30 @setRuntimeSafety(false);26 @setRuntimeSafety(false);
31 var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined;27 var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined;
...@@ -40,8 +36,8 @@ pub fn OutStream(comptime WriteError: type) type {...@@ -40,8 +36,8 @@ pub fn OutStream(comptime WriteError: type) type {
40 }36 }
4137
42 pub fn writeByte(self: *Self, byte: u8) Error!void {38 pub fn writeByte(self: *Self, byte: u8) Error!void {
43 const slice = @as(*const [1]u8, &byte)[0..];39 const array = [1]u8{byte};
44 return self.writeFn(self, slice);40 return self.write(&array);
45 }41 }
4642
47 pub fn writeByteNTimes(self: *Self, byte: u8, n: usize) Error!void {43 pub fn writeByteNTimes(self: *Self, byte: u8, n: usize) Error!void {
...@@ -51,7 +47,7 @@ pub fn OutStream(comptime WriteError: type) type {...@@ -51,7 +47,7 @@ pub fn OutStream(comptime WriteError: type) type {
51 var remaining: usize = n;47 var remaining: usize = n;
52 while (remaining > 0) {48 while (remaining > 0) {
53 const to_write = std.math.min(remaining, bytes.len);49 const to_write = std.math.min(remaining, bytes.len);
54 try self.writeFn(self, bytes[0..to_write]);50 try self.write(bytes[0..to_write]);
55 remaining -= to_write;51 remaining -= to_write;
56 }52 }
57 }53 }
...@@ -60,32 +56,32 @@ pub fn OutStream(comptime WriteError: type) type {...@@ -60,32 +56,32 @@ pub fn OutStream(comptime WriteError: type) type {
60 pub fn writeIntNative(self: *Self, comptime T: type, value: T) Error!void {56 pub fn writeIntNative(self: *Self, comptime T: type, value: T) Error!void {
61 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;57 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
62 mem.writeIntNative(T, &bytes, value);58 mem.writeIntNative(T, &bytes, value);
63 return self.writeFn(self, &bytes);59 return self.write(&bytes);
64 }60 }
6561
66 /// Write a foreign-endian integer.62 /// Write a foreign-endian integer.
67 pub fn writeIntForeign(self: *Self, comptime T: type, value: T) Error!void {63 pub fn writeIntForeign(self: *Self, comptime T: type, value: T) Error!void {
68 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;64 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
69 mem.writeIntForeign(T, &bytes, value);65 mem.writeIntForeign(T, &bytes, value);
70 return self.writeFn(self, &bytes);66 return self.write(&bytes);
71 }67 }
7268
73 pub fn writeIntLittle(self: *Self, comptime T: type, value: T) Error!void {69 pub fn writeIntLittle(self: *Self, comptime T: type, value: T) Error!void {
74 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;70 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
75 mem.writeIntLittle(T, &bytes, value);71 mem.writeIntLittle(T, &bytes, value);
76 return self.writeFn(self, &bytes);72 return self.write(&bytes);
77 }73 }
7874
79 pub fn writeIntBig(self: *Self, comptime T: type, value: T) Error!void {75 pub fn writeIntBig(self: *Self, comptime T: type, value: T) Error!void {
80 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;76 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
81 mem.writeIntBig(T, &bytes, value);77 mem.writeIntBig(T, &bytes, value);
82 return self.writeFn(self, &bytes);78 return self.write(&bytes);
83 }79 }
8480
85 pub fn writeInt(self: *Self, comptime T: type, value: T, endian: builtin.Endian) Error!void {81 pub fn writeInt(self: *Self, comptime T: type, value: T, endian: builtin.Endian) Error!void {
86 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;82 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
87 mem.writeInt(T, &bytes, value, endian);83 mem.writeInt(T, &bytes, value, endian);
88 return self.writeFn(self, &bytes);84 return self.write(&bytes);
89 }85 }
90 };86 };
91}87}
lib/std/linked_list.zig+3-6
...@@ -18,12 +18,11 @@ pub fn SinglyLinkedList(comptime T: type) type {...@@ -18,12 +18,11 @@ pub fn SinglyLinkedList(comptime T: type) type {
1818
19 /// Node inside the linked list wrapping the actual data.19 /// Node inside the linked list wrapping the actual data.
20 pub const Node = struct {20 pub const Node = struct {
21 next: ?*Node,21 next: ?*Node = null,
22 data: T,22 data: T,
2323
24 pub fn init(data: T) Node {24 pub fn init(data: T) Node {
25 return Node{25 return Node{
26 .next = null,
27 .data = data,26 .data = data,
28 };27 };
29 }28 }
...@@ -196,14 +195,12 @@ pub fn TailQueue(comptime T: type) type {...@@ -196,14 +195,12 @@ pub fn TailQueue(comptime T: type) type {
196195
197 /// Node inside the linked list wrapping the actual data.196 /// Node inside the linked list wrapping the actual data.
198 pub const Node = struct {197 pub const Node = struct {
199 prev: ?*Node,198 prev: ?*Node = null,
200 next: ?*Node,199 next: ?*Node = null,
201 data: T,200 data: T,
202201
203 pub fn init(data: T) Node {202 pub fn init(data: T) Node {
204 return Node{203 return Node{
205 .prev = null,
206 .next = null,
207 .data = data,204 .data = data,
208 };205 };
209 }206 }
lib/std/os.zig+179-26
...@@ -169,7 +169,12 @@ fn getRandomBytesDevURandom(buf: []u8) !void {...@@ -169,7 +169,12 @@ fn getRandomBytesDevURandom(buf: []u8) !void {
169 return error.NoDevice;169 return error.NoDevice;
170 }170 }
171171
172 const stream = &std.fs.File.openHandle(fd).inStream().stream;172 const file = std.fs.File{
173 .handle = fd,
174 .io_mode = .blocking,
175 .async_block_allowed = std.fs.File.async_block_allowed_yes,
176 };
177 const stream = &file.inStream().stream;
173 stream.readNoEof(buf) catch return error.Unexpected;178 stream.readNoEof(buf) catch return error.Unexpected;
174}179}
175180
...@@ -293,7 +298,7 @@ pub const ReadError = error{...@@ -293,7 +298,7 @@ pub const ReadError = error{
293/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.298/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
294pub fn read(fd: fd_t, buf: []u8) ReadError!usize {299pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
295 if (builtin.os == .windows) {300 if (builtin.os == .windows) {
296 return windows.ReadFile(fd, buf);301 return windows.ReadFile(fd, buf, null);
297 }302 }
298303
299 if (builtin.os == .wasi and !builtin.link_libc) {304 if (builtin.os == .wasi and !builtin.link_libc) {
...@@ -335,9 +340,37 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {...@@ -335,9 +340,37 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
335}340}
336341
337/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.342/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
338/// If the application has a global event loop enabled, EAGAIN is handled343///
339/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.344/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
345/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
346/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
347/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
348///
349/// This operation is non-atomic on the following systems:
350/// * Windows
351/// On these systems, the read races with concurrent writes to the same file descriptor.
340pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {352pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
353 if (builtin.os == .windows) {
354 // TODO batch these into parallel requests
355 var off: usize = 0;
356 var iov_i: usize = 0;
357 var inner_off: usize = 0;
358 while (true) {
359 const v = iov[iov_i];
360 const amt_read = try read(fd, v.iov_base[inner_off .. v.iov_len - inner_off]);
361 off += amt_read;
362 inner_off += amt_read;
363 if (inner_off == v.len) {
364 iov_i += 1;
365 inner_off = 0;
366 if (iov_i == iov.len) {
367 return off;
368 }
369 }
370 if (amt_read == 0) return off; // EOF
371 } else unreachable; // TODO https://github.com/ziglang/zig/issues/707
372 }
373
341 while (true) {374 while (true) {
342 // TODO handle the case when iov_len is too large and get rid of this @intCast375 // TODO handle the case when iov_len is too large and get rid of this @intCast
343 const rc = system.readv(fd, iov.ptr, @intCast(u32, iov.len));376 const rc = system.readv(fd, iov.ptr, @intCast(u32, iov.len));
...@@ -363,8 +396,56 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {...@@ -363,8 +396,56 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
363}396}
364397
365/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.398/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
366/// If the application has a global event loop enabled, EAGAIN is handled399///
367/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.400/// Retries when interrupted by a signal.
401///
402/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
403/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
404/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
405/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
406pub fn pread(fd: fd_t, buf: []u8, offset: u64) ReadError!usize {
407 if (builtin.os == .windows) {
408 return windows.ReadFile(fd, buf, offset);
409 }
410
411 while (true) {
412 const rc = system.pread(fd, buf.ptr, buf.len, offset);
413 switch (errno(rc)) {
414 0 => return @intCast(usize, rc),
415 EINTR => continue,
416 EINVAL => unreachable,
417 EFAULT => unreachable,
418 EAGAIN => if (std.event.Loop.instance) |loop| {
419 loop.waitUntilFdReadable(fd);
420 continue;
421 } else {
422 return error.WouldBlock;
423 },
424 EBADF => unreachable, // Always a race condition.
425 EIO => return error.InputOutput,
426 EISDIR => return error.IsDir,
427 ENOBUFS => return error.SystemResources,
428 ENOMEM => return error.SystemResources,
429 ECONNRESET => return error.ConnectionResetByPeer,
430 else => |err| return unexpectedErrno(err),
431 }
432 }
433 return index;
434}
435
436/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
437///
438/// Retries when interrupted by a signal.
439///
440/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
441/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
442/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
443/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
444///
445/// This operation is non-atomic on the following systems:
446/// * Darwin
447/// * Windows
448/// On these systems, the read races with concurrent writes to the same file descriptor.
368pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {449pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {
369 if (comptime std.Target.current.isDarwin()) {450 if (comptime std.Target.current.isDarwin()) {
370 // Darwin does not have preadv but it does have pread.451 // Darwin does not have preadv but it does have pread.
...@@ -409,6 +490,28 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {...@@ -409,6 +490,28 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {
409 }490 }
410 }491 }
411 }492 }
493
494 if (builtin.os == .windows) {
495 // TODO batch these into parallel requests
496 var off: usize = 0;
497 var iov_i: usize = 0;
498 var inner_off: usize = 0;
499 while (true) {
500 const v = iov[iov_i];
501 const amt_read = try pread(fd, v.iov_base[inner_off .. v.iov_len - inner_off], offset + off);
502 off += amt_read;
503 inner_off += amt_read;
504 if (inner_off == v.len) {
505 iov_i += 1;
506 inner_off = 0;
507 if (iov_i == iov.len) {
508 return off;
509 }
510 }
511 if (amt_read == 0) return off; // EOF
512 } else unreachable; // TODO https://github.com/ziglang/zig/issues/707
513 }
514
412 while (true) {515 while (true) {
413 // TODO handle the case when iov_len is too large and get rid of this @intCast516 // TODO handle the case when iov_len is too large and get rid of this @intCast
414 const rc = system.preadv(fd, iov.ptr, @intCast(u32, iov.len), offset);517 const rc = system.preadv(fd, iov.ptr, @intCast(u32, iov.len), offset);
...@@ -451,11 +554,9 @@ pub const WriteError = error{...@@ -451,11 +554,9 @@ pub const WriteError = error{
451/// Write to a file descriptor. Keeps trying if it gets interrupted.554/// Write to a file descriptor. Keeps trying if it gets interrupted.
452/// If the application has a global event loop enabled, EAGAIN is handled555/// If the application has a global event loop enabled, EAGAIN is handled
453/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.556/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
454/// TODO evented I/O integration is disabled until
455/// https://github.com/ziglang/zig/issues/3557 is solved.
456pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {557pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {
457 if (builtin.os == .windows) {558 if (builtin.os == .windows) {
458 return windows.WriteFile(fd, bytes);559 return windows.WriteFile(fd, bytes, null);
459 }560 }
460561
461 if (builtin.os == .wasi and !builtin.link_libc) {562 if (builtin.os == .wasi and !builtin.link_libc) {
...@@ -488,14 +589,12 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {...@@ -488,14 +589,12 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {
488 EINTR => continue,589 EINTR => continue,
489 EINVAL => unreachable,590 EINVAL => unreachable,
490 EFAULT => unreachable,591 EFAULT => unreachable,
491 // TODO https://github.com/ziglang/zig/issues/3557592 EAGAIN => if (std.event.Loop.instance) |loop| {
492 EAGAIN => return error.WouldBlock,593 loop.waitUntilFdWritable(fd);
493 //EAGAIN => if (std.event.Loop.instance) |loop| {594 continue;
494 // loop.waitUntilFdWritable(fd);595 } else {
495 // continue;596 return error.WouldBlock;
496 //} else {597 },
497 // return error.WouldBlock;
498 //},
499 EBADF => unreachable, // Always a race condition.598 EBADF => unreachable, // Always a race condition.
500 EDESTADDRREQ => unreachable, // `connect` was never called.599 EDESTADDRREQ => unreachable, // `connect` was never called.
501 EDQUOT => return error.DiskQuota,600 EDQUOT => return error.DiskQuota,
...@@ -540,8 +639,57 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!void {...@@ -540,8 +639,57 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!void {
540 }639 }
541}640}
542641
642/// Write to a file descriptor, with a position offset.
643///
644/// Retries when interrupted by a signal.
645///
646/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
647/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
648/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
649/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
650pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) WriteError!void {
651 if (comptime std.Target.current.isWindows()) {
652 return windows.WriteFile(fd, bytes, offset);
653 }
654
655 while (true) {
656 const rc = system.pwrite(fd, bytes.ptr, bytes.len, offset);
657 switch (errno(rc)) {
658 0 => return,
659 EINTR => continue,
660 EINVAL => unreachable,
661 EFAULT => unreachable,
662 EAGAIN => if (std.event.Loop.instance) |loop| {
663 loop.waitUntilFdWritable(fd);
664 continue;
665 } else {
666 return error.WouldBlock;
667 },
668 EBADF => unreachable, // Always a race condition.
669 EDESTADDRREQ => unreachable, // `connect` was never called.
670 EDQUOT => return error.DiskQuota,
671 EFBIG => return error.FileTooBig,
672 EIO => return error.InputOutput,
673 ENOSPC => return error.NoSpaceLeft,
674 EPERM => return error.AccessDenied,
675 EPIPE => return error.BrokenPipe,
676 else => |err| return unexpectedErrno(err),
677 }
678 }
679}
680
543/// Write multiple buffers to a file descriptor, with a position offset.681/// Write multiple buffers to a file descriptor, with a position offset.
544/// Keeps trying if it gets interrupted.682///
683/// Retries when interrupted by a signal.
684///
685/// If the application has a global event loop enabled, EAGAIN is handled
686/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
687///
688/// This operation is non-atomic on the following systems:
689/// * Darwin
690/// * Windows
691/// On these systems, the write races with concurrent writes to the same file descriptor, and
692/// the file can be in a partially written state when an error occurs.
545pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void {693pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void {
546 if (comptime std.Target.current.isDarwin()) {694 if (comptime std.Target.current.isDarwin()) {
547 // Darwin does not have pwritev but it does have pwrite.695 // Darwin does not have pwritev but it does have pwrite.
...@@ -589,6 +737,15 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void...@@ -589,6 +737,15 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void
589 }737 }
590 }738 }
591739
740 if (comptime std.Target.current.isWindows()) {
741 var off = offset;
742 for (iov) |item| {
743 try pwrite(fd, item.iov_base[0..item.iov_len], off);
744 off += buf.len;
745 }
746 return;
747 }
748
592 while (true) {749 while (true) {
593 // TODO handle the case when iov_len is too large and get rid of this @intCast750 // TODO handle the case when iov_len is too large and get rid of this @intCast
594 const rc = system.pwritev(fd, iov.ptr, @intCast(u32, iov.len), offset);751 const rc = system.pwritev(fd, iov.ptr, @intCast(u32, iov.len), offset);
...@@ -694,7 +851,7 @@ pub fn openC(file_path: [*:0]const u8, flags: u32, perm: usize) OpenError!fd_t {...@@ -694,7 +851,7 @@ pub fn openC(file_path: [*:0]const u8, flags: u32, perm: usize) OpenError!fd_t {
694/// Open and possibly create a file. Keeps trying if it gets interrupted.851/// Open and possibly create a file. Keeps trying if it gets interrupted.
695/// `file_path` is relative to the open directory handle `dir_fd`.852/// `file_path` is relative to the open directory handle `dir_fd`.
696/// See also `openatC`.853/// See also `openatC`.
697pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: usize) OpenError!fd_t {854pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t {
698 const file_path_c = try toPosixPath(file_path);855 const file_path_c = try toPosixPath(file_path);
699 return openatC(dir_fd, &file_path_c, flags, mode);856 return openatC(dir_fd, &file_path_c, flags, mode);
700}857}
...@@ -702,7 +859,7 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: usize) Open...@@ -702,7 +859,7 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: usize) Open
702/// Open and possibly create a file. Keeps trying if it gets interrupted.859/// Open and possibly create a file. Keeps trying if it gets interrupted.
703/// `file_path` is relative to the open directory handle `dir_fd`.860/// `file_path` is relative to the open directory handle `dir_fd`.
704/// See also `openat`.861/// See also `openat`.
705pub fn openatC(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: usize) OpenError!fd_t {862pub fn openatC(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) OpenError!fd_t {
706 while (true) {863 while (true) {
707 const rc = system.openat(dir_fd, file_path, flags, mode);864 const rc = system.openat(dir_fd, file_path, flags, mode);
708 switch (errno(rc)) {865 switch (errno(rc)) {
...@@ -3328,9 +3485,7 @@ pub fn getrusage(who: i32) rusage {...@@ -3328,9 +3485,7 @@ pub fn getrusage(who: i32) rusage {
3328 }3485 }
3329}3486}
33303487
3331pub const TermiosGetError = error{3488pub const TermiosGetError = error{NotATerminal} || UnexpectedError;
3332 NotATerminal,
3333} || UnexpectedError;
33343489
3335pub fn tcgetattr(handle: fd_t) TermiosGetError!termios {3490pub fn tcgetattr(handle: fd_t) TermiosGetError!termios {
3336 var term: termios = undefined;3491 var term: termios = undefined;
...@@ -3342,9 +3497,7 @@ pub fn tcgetattr(handle: fd_t) TermiosGetError!termios {...@@ -3342,9 +3497,7 @@ pub fn tcgetattr(handle: fd_t) TermiosGetError!termios {
3342 }3497 }
3343}3498}
33443499
3345pub const TermiosSetError = TermiosGetError || error{3500pub const TermiosSetError = TermiosGetError || error{ProcessOrphaned};
3346 ProcessOrphaned,
3347};
33483501
3349pub fn tcsetattr(handle: fd_t, optional_action: TCSA, termios_p: termios) TermiosSetError!void {3502pub fn tcsetattr(handle: fd_t, optional_action: TCSA, termios_p: termios) TermiosSetError!void {
3350 while (true) {3503 while (true) {
lib/std/os/bits/darwin.zig+1
...@@ -4,6 +4,7 @@ const maxInt = std.math.maxInt;...@@ -4,6 +4,7 @@ const maxInt = std.math.maxInt;
44
5pub const fd_t = c_int;5pub const fd_t = c_int;
6pub const pid_t = c_int;6pub const pid_t = c_int;
7pub const mode_t = c_uint;
78
8pub const in_port_t = u16;9pub const in_port_t = u16;
9pub const sa_family_t = u8;10pub const sa_family_t = u8;
lib/std/os/bits/dragonfly.zig+1
...@@ -7,6 +7,7 @@ pub fn S_ISCHR(m: u32) bool {...@@ -7,6 +7,7 @@ pub fn S_ISCHR(m: u32) bool {
7pub const fd_t = c_int;7pub const fd_t = c_int;
8pub const pid_t = c_int;8pub const pid_t = c_int;
9pub const off_t = c_long;9pub const off_t = c_long;
10pub const mode_t = c_uint;
1011
11pub const ENOTSUP = EOPNOTSUPP;12pub const ENOTSUP = EOPNOTSUPP;
12pub const EWOULDBLOCK = EAGAIN;13pub const EWOULDBLOCK = EAGAIN;
lib/std/os/bits/freebsd.zig+1
...@@ -3,6 +3,7 @@ const maxInt = std.math.maxInt;...@@ -3,6 +3,7 @@ const maxInt = std.math.maxInt;
33
4pub const fd_t = c_int;4pub const fd_t = c_int;
5pub const pid_t = c_int;5pub const pid_t = c_int;
6pub const mode_t = c_uint;
67
7pub const socklen_t = u32;8pub const socklen_t = u32;
89
lib/std/os/bits/linux/x86_64.zig+2
...@@ -12,6 +12,8 @@ const socklen_t = linux.socklen_t;...@@ -12,6 +12,8 @@ const socklen_t = linux.socklen_t;
12const iovec = linux.iovec;12const iovec = linux.iovec;
13const iovec_const = linux.iovec_const;13const iovec_const = linux.iovec_const;
1414
15pub const mode_t = usize;
16
15pub const SYS_read = 0;17pub const SYS_read = 0;
16pub const SYS_write = 1;18pub const SYS_write = 1;
17pub const SYS_open = 2;19pub const SYS_open = 2;
lib/std/os/bits/netbsd.zig+1
...@@ -3,6 +3,7 @@ const maxInt = std.math.maxInt;...@@ -3,6 +3,7 @@ const maxInt = std.math.maxInt;
33
4pub const fd_t = c_int;4pub const fd_t = c_int;
5pub const pid_t = c_int;5pub const pid_t = c_int;
6pub const mode_t = c_uint;
67
7/// Renamed from `kevent` to `Kevent` to avoid conflict with function name.8/// Renamed from `kevent` to `Kevent` to avoid conflict with function name.
8pub const Kevent = extern struct {9pub const Kevent = extern struct {
lib/std/os/bits/wasi.zig+1
...@@ -130,6 +130,7 @@ pub const EVENTTYPE_FD_WRITE: eventtype_t = 2;...@@ -130,6 +130,7 @@ pub const EVENTTYPE_FD_WRITE: eventtype_t = 2;
130pub const exitcode_t = u32;130pub const exitcode_t = u32;
131131
132pub const fd_t = u32;132pub const fd_t = u32;
133pub const mode_t = u32;
133134
134pub const fdflags_t = u16;135pub const fdflags_t = u16;
135pub const FDFLAG_APPEND: fdflags_t = 0x0001;136pub const FDFLAG_APPEND: fdflags_t = 0x0001;
lib/std/os/bits/windows.zig+1
...@@ -5,6 +5,7 @@ const ws2_32 = @import("../windows/ws2_32.zig");...@@ -5,6 +5,7 @@ const ws2_32 = @import("../windows/ws2_32.zig");
55
6pub const fd_t = HANDLE;6pub const fd_t = HANDLE;
7pub const pid_t = HANDLE;7pub const pid_t = HANDLE;
8pub const mode_t = u0;
89
9pub const PATH_MAX = 260;10pub const PATH_MAX = 260;
1011
lib/std/os/windows.zig+128-29
...@@ -344,24 +344,77 @@ pub fn FindClose(hFindFile: HANDLE) void {...@@ -344,24 +344,77 @@ pub fn FindClose(hFindFile: HANDLE) void {
344 assert(kernel32.FindClose(hFindFile) != 0);344 assert(kernel32.FindClose(hFindFile) != 0);
345}345}
346346
347pub const ReadFileError = error{Unexpected};347pub const ReadFileError = error{
348348 OperationAborted,
349pub fn ReadFile(in_hFile: HANDLE, buffer: []u8) ReadFileError!usize {349 BrokenPipe,
350 var index: usize = 0;350 Unexpected,
351 while (index < buffer.len) {351};
352 const want_read_count = @intCast(DWORD, math.min(@as(DWORD, maxInt(DWORD)), buffer.len - index));352
353 var amt_read: DWORD = undefined;353/// If buffer's length exceeds what a Windows DWORD integer can hold, it will be broken into
354 if (kernel32.ReadFile(in_hFile, buffer.ptr + index, want_read_count, &amt_read, null) == 0) {354/// multiple non-atomic reads.
355 switch (kernel32.GetLastError()) {355pub fn ReadFile(in_hFile: HANDLE, buffer: []u8, offset: ?u64) ReadFileError!usize {
356 .OPERATION_ABORTED => continue,356 if (std.event.Loop.instance) |loop| {
357 .BROKEN_PIPE => return index,357 // TODO support async ReadFile with no offset
358 else => |err| return unexpectedError(err),358 const off = offset.?;
359 var resume_node = std.event.Loop.ResumeNode.Basic{
360 .base = .{
361 .id = .Basic,
362 .handle = @frame(),
363 .overlapped = OVERLAPPED{
364 .Internal = 0,
365 .InternalHigh = 0,
366 .Offset = @truncate(u32, off),
367 .OffsetHigh = @truncate(u32, off >> 32),
368 .hEvent = null,
369 },
370 },
371 };
372 // TODO only call create io completion port once per fd
373 _ = windows.CreateIoCompletionPort(fd, loop.os_data.io_port, undefined, undefined) catch undefined;
374 loop.beginOneEvent();
375 suspend {
376 // TODO handle buffer bigger than DWORD can hold
377 _ = windows.kernel32.ReadFile(fd, buffer.ptr, @intCast(windows.DWORD, buffer.len), null, &resume_node.base.overlapped);
378 }
379 var bytes_transferred: windows.DWORD = undefined;
380 if (windows.kernel32.GetOverlappedResult(fd, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) {
381 switch (windows.kernel32.GetLastError()) {
382 .IO_PENDING => unreachable,
383 .OPERATION_ABORTED => return error.OperationAborted,
384 .BROKEN_PIPE => return error.BrokenPipe,
385 .HANDLE_EOF => return @as(usize, bytes_transferred),
386 else => |err| return windows.unexpectedError(err),
387 }
388 }
389 return @as(usize, bytes_transferred);
390 } else {
391 var index: usize = 0;
392 while (index < buffer.len) {
393 const want_read_count = @intCast(DWORD, math.min(@as(DWORD, maxInt(DWORD)), buffer.len - index));
394 var amt_read: DWORD = undefined;
395 var overlapped_data: OVERLAPPED = undefined;
396 const overlapped: ?*OVERLAPPED = if (offset) |off| blk: {
397 overlapped_data = .{
398 .Internal = 0,
399 .InternalHigh = 0,
400 .Offset = @truncate(u32, off + index),
401 .OffsetHigh = @truncate(u32, (off + index) >> 32),
402 .hEvent = null,
403 };
404 break :blk &overlapped_data;
405 } else null;
406 if (kernel32.ReadFile(in_hFile, buffer.ptr + index, want_read_count, &amt_read, overlapped) == 0) {
407 switch (kernel32.GetLastError()) {
408 .OPERATION_ABORTED => continue,
409 .BROKEN_PIPE => return index,
410 else => |err| return unexpectedError(err),
411 }
359 }412 }
413 if (amt_read == 0) return index;
414 index += amt_read;
360 }415 }
361 if (amt_read == 0) return index;416 return index;
362 index += amt_read;
363 }417 }
364 return index;
365}418}
366419
367pub const WriteFileError = error{420pub const WriteFileError = error{
...@@ -371,20 +424,66 @@ pub const WriteFileError = error{...@@ -371,20 +424,66 @@ pub const WriteFileError = error{
371 Unexpected,424 Unexpected,
372};425};
373426
374/// This function is for blocking file descriptors only. For non-blocking, see427pub fn WriteFile(handle: HANDLE, bytes: []const u8, offset: ?u64) WriteFileError!void {
375/// `WriteFileAsync`.428 if (std.event.Loop.instance) |loop| {
376pub fn WriteFile(handle: HANDLE, bytes: []const u8) WriteFileError!void {429 // TODO support async WriteFile with no offset
377 var bytes_written: DWORD = undefined;430 const off = offset.?;
378 // TODO replace this @intCast with a loop that writes all the bytes431 var resume_node = std.event.Loop.ResumeNode.Basic{
379 if (kernel32.WriteFile(handle, bytes.ptr, @intCast(u32, bytes.len), &bytes_written, null) == 0) {432 .base = .{
380 switch (kernel32.GetLastError()) {433 .id = .Basic,
381 .INVALID_USER_BUFFER => return error.SystemResources,434 .handle = @frame(),
382 .NOT_ENOUGH_MEMORY => return error.SystemResources,435 .overlapped = OVERLAPPED{
383 .OPERATION_ABORTED => return error.OperationAborted,436 .Internal = 0,
384 .NOT_ENOUGH_QUOTA => return error.SystemResources,437 .InternalHigh = 0,
385 .IO_PENDING => unreachable, // this function is for blocking files only438 .Offset = @truncate(u32, off),
386 .BROKEN_PIPE => return error.BrokenPipe,439 .OffsetHigh = @truncate(u32, off >> 32),
387 else => |err| return unexpectedError(err),440 .hEvent = null,
441 },
442 },
443 };
444 // TODO only call create io completion port once per fd
445 _ = CreateIoCompletionPort(fd, loop.os_data.io_port, undefined, undefined);
446 loop.beginOneEvent();
447 suspend {
448 // TODO replace this @intCast with a loop that writes all the bytes
449 _ = kernel32.WriteFile(fd, bytes.ptr, @intCast(windows.DWORD, bytes.len), null, &resume_node.base.overlapped);
450 }
451 var bytes_transferred: windows.DWORD = undefined;
452 if (kernel32.GetOverlappedResult(fd, &resume_node.base.overlapped, &bytes_transferred, FALSE) == 0) {
453 switch (kernel32.GetLastError()) {
454 .IO_PENDING => unreachable,
455 .INVALID_USER_BUFFER => return error.SystemResources,
456 .NOT_ENOUGH_MEMORY => return error.SystemResources,
457 .OPERATION_ABORTED => return error.OperationAborted,
458 .NOT_ENOUGH_QUOTA => return error.SystemResources,
459 .BROKEN_PIPE => return error.BrokenPipe,
460 else => |err| return windows.unexpectedError(err),
461 }
462 }
463 } else {
464 var bytes_written: DWORD = undefined;
465 var overlapped_data: OVERLAPPED = undefined;
466 const overlapped: ?*OVERLAPPED = if (offset) |off| blk: {
467 overlapped_data = .{
468 .Internal = 0,
469 .InternalHigh = 0,
470 .Offset = @truncate(u32, off),
471 .OffsetHigh = @truncate(u32, off >> 32),
472 .hEvent = null,
473 };
474 break :blk &overlapped_data;
475 } else null;
476 // TODO replace this @intCast with a loop that writes all the bytes
477 if (kernel32.WriteFile(handle, bytes.ptr, @intCast(u32, bytes.len), &bytes_written, overlapped) == 0) {
478 switch (kernel32.GetLastError()) {
479 .INVALID_USER_BUFFER => return error.SystemResources,
480 .NOT_ENOUGH_MEMORY => return error.SystemResources,
481 .OPERATION_ABORTED => return error.OperationAborted,
482 .NOT_ENOUGH_QUOTA => return error.SystemResources,
483 .IO_PENDING => unreachable, // this function is for blocking files only
484 .BROKEN_PIPE => return error.BrokenPipe,
485 else => |err| return unexpectedError(err),
486 }
388 }487 }
389 }488 }
390}489}
lib/std/special/test_runner.zig+24-2
...@@ -2,7 +2,7 @@ const std = @import("std");...@@ -2,7 +2,7 @@ const std = @import("std");
2const io = std.io;2const io = std.io;
3const builtin = @import("builtin");3const builtin = @import("builtin");
44
5pub const io_mode = builtin.test_io_mode;5pub const io_mode: io.Mode = builtin.test_io_mode;
66
7pub fn main() anyerror!void {7pub fn main() anyerror!void {
8 const test_fn_list = builtin.test_functions;8 const test_fn_list = builtin.test_functions;
...@@ -14,6 +14,11 @@ pub fn main() anyerror!void {...@@ -14,6 +14,11 @@ pub fn main() anyerror!void {
14 error.TimerUnsupported => @panic("timer unsupported"),14 error.TimerUnsupported => @panic("timer unsupported"),
15 };15 };
1616
17 var async_frame_buffer: []align(std.Target.stack_align) u8 = undefined;
18 // TODO this is on the next line (using `undefined` above) because otherwise zig incorrectly
19 // ignores the alignment of the slice.
20 async_frame_buffer = &[_]u8{};
21
17 for (test_fn_list) |test_fn, i| {22 for (test_fn_list) |test_fn, i| {
18 std.testing.base_allocator_instance.reset();23 std.testing.base_allocator_instance.reset();
1924
...@@ -23,7 +28,24 @@ pub fn main() anyerror!void {...@@ -23,7 +28,24 @@ pub fn main() anyerror!void {
23 if (progress.terminal == null) {28 if (progress.terminal == null) {
24 std.debug.warn("{}/{} {}...", .{ i + 1, test_fn_list.len, test_fn.name });29 std.debug.warn("{}/{} {}...", .{ i + 1, test_fn_list.len, test_fn.name });
25 }30 }
26 if (test_fn.func()) |_| {31 const result = if (test_fn.async_frame_size) |size| switch (io_mode) {
32 .evented => blk: {
33 if (async_frame_buffer.len < size) {
34 std.heap.page_allocator.free(async_frame_buffer);
35 async_frame_buffer = try std.heap.page_allocator.alignedAlloc(u8, std.Target.stack_align, size);
36 }
37 const casted_fn = @ptrCast(async fn () anyerror!void, test_fn.func);
38 break :blk await @asyncCall(async_frame_buffer, {}, casted_fn);
39 },
40 .blocking => {
41 skip_count += 1;
42 test_node.end();
43 progress.log("{}...SKIP (async test)\n", .{test_fn.name});
44 if (progress.terminal == null) std.debug.warn("SKIP (async test)\n", .{});
45 continue;
46 },
47 } else test_fn.func();
48 if (result) |_| {
27 ok_count += 1;49 ok_count += 1;
28 test_node.end();50 test_node.end();
29 std.testing.allocator_instance.validate() catch |err| switch (err) {51 std.testing.allocator_instance.validate() catch |err| switch (err) {
src/analyze.cpp+16-4
...@@ -6144,6 +6144,15 @@ static bool scope_needs_spill(Scope *scope) {...@@ -6144,6 +6144,15 @@ static bool scope_needs_spill(Scope *scope) {
6144 zig_unreachable();6144 zig_unreachable();
6145}6145}
61466146
6147static ZigType *resolve_type_isf(ZigType *ty) {
6148 if (ty->id != ZigTypeIdPointer) return ty;
6149 InferredStructField *isf = ty->data.pointer.inferred_struct_field;
6150 if (isf == nullptr) return ty;
6151 TypeStructField *field = find_struct_type_field(isf->inferred_struct_type, isf->field_name);
6152 assert(field != nullptr);
6153 return field->type_entry;
6154}
6155
6147static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {6156static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
6148 Error err;6157 Error err;
61496158
...@@ -6245,6 +6254,9 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {...@@ -6245,6 +6254,9 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
6245 }6254 }
6246 ZigFn *callee = call->fn_entry;6255 ZigFn *callee = call->fn_entry;
6247 if (callee == nullptr) {6256 if (callee == nullptr) {
6257 if (call->fn_ref->value->type->data.fn.fn_type_id.cc != CallingConventionAsync) {
6258 continue;
6259 }
6248 add_node_error(g, call->base.base.source_node,6260 add_node_error(g, call->base.base.source_node,
6249 buf_sprintf("function is not comptime-known; @asyncCall required"));6261 buf_sprintf("function is not comptime-known; @asyncCall required"));
6250 return ErrorSemanticAnalyzeFail;6262 return ErrorSemanticAnalyzeFail;
...@@ -6402,7 +6414,7 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {...@@ -6402,7 +6414,7 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
6402 } else {6414 } else {
6403 param_name = buf_sprintf("@arg%" ZIG_PRI_usize, arg_i);6415 param_name = buf_sprintf("@arg%" ZIG_PRI_usize, arg_i);
6404 }6416 }
6405 ZigType *param_type = param_info->type;6417 ZigType *param_type = resolve_type_isf(param_info->type);
6406 if ((err = type_resolve(g, param_type, ResolveStatusSizeKnown))) {6418 if ((err = type_resolve(g, param_type, ResolveStatusSizeKnown))) {
6407 return err;6419 return err;
6408 }6420 }
...@@ -6421,7 +6433,7 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {...@@ -6421,7 +6433,7 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
6421 instruction->field_index = SIZE_MAX;6433 instruction->field_index = SIZE_MAX;
6422 ZigType *ptr_type = instruction->base.value->type;6434 ZigType *ptr_type = instruction->base.value->type;
6423 assert(ptr_type->id == ZigTypeIdPointer);6435 assert(ptr_type->id == ZigTypeIdPointer);
6424 ZigType *child_type = ptr_type->data.pointer.child_type;6436 ZigType *child_type = resolve_type_isf(ptr_type->data.pointer.child_type);
6425 if (!type_has_bits(child_type))6437 if (!type_has_bits(child_type))
6426 continue;6438 continue;
6427 if (instruction->base.base.ref_count == 0)6439 if (instruction->base.base.ref_count == 0)
...@@ -6448,8 +6460,6 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {...@@ -6448,8 +6460,6 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
6448 }6460 }
6449 instruction->field_index = fields.length;6461 instruction->field_index = fields.length;
64506462
6451 src_assert(child_type->id != ZigTypeIdPointer || child_type->data.pointer.inferred_struct_field == nullptr,
6452 instruction->base.base.source_node);
6453 fields.append({name, child_type, instruction->align});6463 fields.append({name, child_type, instruction->align});
6454 }6464 }
64556465
...@@ -8251,6 +8261,8 @@ static void resolve_llvm_types_struct(CodeGen *g, ZigType *struct_type, ResolveS...@@ -8251,6 +8261,8 @@ static void resolve_llvm_types_struct(CodeGen *g, ZigType *struct_type, ResolveS
8251 size_t debug_field_index = 0;8261 size_t debug_field_index = 0;
8252 for (size_t i = 0; i < field_count; i += 1) {8262 for (size_t i = 0; i < field_count; i += 1) {
8253 TypeStructField *field = struct_type->data.structure.fields[i];8263 TypeStructField *field = struct_type->data.structure.fields[i];
8264 //fprintf(stderr, "%s at gen index %zu\n", buf_ptr(field->name), field->gen_index);
8265
8254 size_t gen_field_index = field->gen_index;8266 size_t gen_field_index = field->gen_index;
8255 if (gen_field_index == SIZE_MAX) {8267 if (gen_field_index == SIZE_MAX) {
8256 continue;8268 continue;
src/codegen.cpp+24-18
...@@ -362,14 +362,16 @@ static uint32_t frame_index_arg(CodeGen *g, ZigType *return_type) {...@@ -362,14 +362,16 @@ static uint32_t frame_index_arg(CodeGen *g, ZigType *return_type) {
362}362}
363363
364// label (grep this): [fn_frame_struct_layout]364// label (grep this): [fn_frame_struct_layout]
365static uint32_t frame_index_trace_stack(CodeGen *g, FnTypeId *fn_type_id) {365static uint32_t frame_index_trace_stack(CodeGen *g, ZigFn *fn) {
366 uint32_t result = frame_index_arg(g, fn_type_id->return_type);366 size_t field_index = 6;
367 for (size_t i = 0; i < fn_type_id->param_count; i += 1) {367 bool have_stack_trace = codegen_fn_has_err_ret_tracing_arg(g, fn->type_entry->data.fn.fn_type_id.return_type);
368 if (type_has_bits(fn_type_id->param_info->type)) {368 if (have_stack_trace) {
369 result += 1;369 field_index += 2;
370 }
371 }370 }
372 return result;371 field_index += fn->type_entry->data.fn.fn_type_id.param_count;
372 ZigType *locals_struct = fn->frame_type->data.frame.locals_struct;
373 TypeStructField *field = locals_struct->data.structure.fields[field_index];
374 return field->gen_index;
373}375}
374376
375377
...@@ -7742,7 +7744,7 @@ static void do_code_gen(CodeGen *g) {...@@ -7742,7 +7744,7 @@ static void do_code_gen(CodeGen *g) {
7742 }7744 }
7743 uint32_t trace_field_index_stack = UINT32_MAX;7745 uint32_t trace_field_index_stack = UINT32_MAX;
7744 if (codegen_fn_has_err_ret_tracing_stack(g, fn_table_entry, true)) {7746 if (codegen_fn_has_err_ret_tracing_stack(g, fn_table_entry, true)) {
7745 trace_field_index_stack = frame_index_trace_stack(g, fn_type_id);7747 trace_field_index_stack = frame_index_trace_stack(g, fn_table_entry);
7746 g->cur_err_ret_trace_val_stack = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr,7748 g->cur_err_ret_trace_val_stack = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr,
7747 trace_field_index_stack, "");7749 trace_field_index_stack, "");
7748 }7750 }
...@@ -9396,22 +9398,13 @@ static void update_test_functions_builtin_decl(CodeGen *g) {...@@ -9396,22 +9398,13 @@ static void update_test_functions_builtin_decl(CodeGen *g) {
9396 for (size_t i = 0; i < g->test_fns.length; i += 1) {9398 for (size_t i = 0; i < g->test_fns.length; i += 1) {
9397 ZigFn *test_fn_entry = g->test_fns.at(i);9399 ZigFn *test_fn_entry = g->test_fns.at(i);
93989400
9399 if (fn_is_async(test_fn_entry)) {
9400 ErrorMsg *msg = add_node_error(g, test_fn_entry->proto_node,
9401 buf_create_from_str("test functions cannot be async"));
9402 add_error_note(g, msg, test_fn_entry->proto_node,
9403 buf_sprintf("this restriction may be lifted in the future. See https://github.com/ziglang/zig/issues/3117 for more details"));
9404 add_async_error_notes(g, msg, test_fn_entry);
9405 continue;
9406 }
9407
9408 ZigValue *this_val = &test_fn_array->data.x_array.data.s_none.elements[i];9401 ZigValue *this_val = &test_fn_array->data.x_array.data.s_none.elements[i];
9409 this_val->special = ConstValSpecialStatic;9402 this_val->special = ConstValSpecialStatic;
9410 this_val->type = struct_type;9403 this_val->type = struct_type;
9411 this_val->parent.id = ConstParentIdArray;9404 this_val->parent.id = ConstParentIdArray;
9412 this_val->parent.data.p_array.array_val = test_fn_array;9405 this_val->parent.data.p_array.array_val = test_fn_array;
9413 this_val->parent.data.p_array.elem_index = i;9406 this_val->parent.data.p_array.elem_index = i;
9414 this_val->data.x_struct.fields = alloc_const_vals_ptrs(2);9407 this_val->data.x_struct.fields = alloc_const_vals_ptrs(3);
94159408
9416 ZigValue *name_field = this_val->data.x_struct.fields[0];9409 ZigValue *name_field = this_val->data.x_struct.fields[0];
9417 ZigValue *name_array_val = create_const_str_lit(g, &test_fn_entry->symbol_name)->data.x_ptr.data.ref.pointee;9410 ZigValue *name_array_val = create_const_str_lit(g, &test_fn_entry->symbol_name)->data.x_ptr.data.ref.pointee;
...@@ -9423,6 +9416,19 @@ static void update_test_functions_builtin_decl(CodeGen *g) {...@@ -9423,6 +9416,19 @@ static void update_test_functions_builtin_decl(CodeGen *g) {
9423 fn_field->data.x_ptr.special = ConstPtrSpecialFunction;9416 fn_field->data.x_ptr.special = ConstPtrSpecialFunction;
9424 fn_field->data.x_ptr.mut = ConstPtrMutComptimeConst;9417 fn_field->data.x_ptr.mut = ConstPtrMutComptimeConst;
9425 fn_field->data.x_ptr.data.fn.fn_entry = test_fn_entry;9418 fn_field->data.x_ptr.data.fn.fn_entry = test_fn_entry;
9419
9420 ZigValue *frame_size_field = this_val->data.x_struct.fields[2];
9421 frame_size_field->type = get_optional_type(g, g->builtin_types.entry_usize);
9422 frame_size_field->special = ConstValSpecialStatic;
9423 frame_size_field->data.x_optional = nullptr;
9424
9425 if (fn_is_async(test_fn_entry)) {
9426 frame_size_field->data.x_optional = create_const_vals(1);
9427 frame_size_field->data.x_optional->special = ConstValSpecialStatic;
9428 frame_size_field->data.x_optional->type = g->builtin_types.entry_usize;
9429 bigint_init_unsigned(&frame_size_field->data.x_optional->data.x_bigint,
9430 test_fn_entry->frame_type->abi_size);
9431 }
9426 }9432 }
9427 report_errors_and_maybe_exit(g);9433 report_errors_and_maybe_exit(g);
94289434