diff options
| -rwxr-xr-x | ci/srht/update_download_page | 8 | ||||
| -rw-r--r-- | lib/std/array_list.zig | 2 | ||||
| -rw-r--r-- | lib/std/builtin.zig | 2 | ||||
| -rw-r--r-- | lib/std/child_process.zig | 2 | ||||
| -rw-r--r-- | lib/std/debug/failing_allocator.zig | 4 | ||||
| -rw-r--r-- | lib/std/event/fs.zig | 1392 | ||||
| -rw-r--r-- | lib/std/fifo.zig | 2 | ||||
| -rw-r--r-- | lib/std/mutex.zig | 4 | ||||
| -rw-r--r-- | lib/std/net.zig | 2 | ||||
| -rw-r--r-- | lib/std/os.zig | 4 | ||||
| -rw-r--r-- | lib/std/os/bits/linux.zig | 1 | ||||
| -rw-r--r-- | lib/std/os/uefi/protocols/hii.zig | 1 | ||||
| -rw-r--r-- | lib/std/os/uefi/status.zig | 38 | ||||
| -rw-r--r-- | lib/std/os/uefi/tables/table_header.zig | 1 | ||||
| -rw-r--r-- | lib/std/os/windows.zig | 5 | ||||
| -rw-r--r-- | lib/std/os/windows/ws2_32.zig | 10 | ||||
| -rw-r--r-- | lib/std/zig/ast.zig | 2 | ||||
| -rw-r--r-- | lib/std/zig/parse.zig | 42 | ||||
| -rw-r--r-- | lib/std/zig/parser_test.zig | 27 | ||||
| -rw-r--r-- | lib/std/zig/render.zig | 9 |
20 files changed, 806 insertions, 752 deletions
diff --git a/ci/srht/update_download_page b/ci/srht/update_download_page index dc6b1bcc9a..1a721bec80 100755 --- a/ci/srht/update_download_page +++ b/ci/srht/update_download_page @@ -73,7 +73,7 @@ cd www.ziglang.org export MASTER_DATE="$(date +%Y-%m-%d)" "../$ZIG" run update-download-page.zig -$S3CMD put -P --add-header="cache-control: public, max-age=31536000, immutable" "../$SRC_TARBALL" s3://ziglang.org/builds/ -$S3CMD put -P "../$LANGREF" s3://ziglang.org/documentation/master/index.html --add-header="Cache-Control: max-age=0, must-revalidate" -$S3CMD put -P www/download/index.html s3://ziglang.org/download/index.html --add-header="Cache-Control: max-age=0, must-revalidate" -$S3CMD put -P www/download/index.json s3://ziglang.org/download/index.json --add-header="Cache-Control: max-age=0, must-revalidate" +$S3CMD put -P --no-mime-magic --add-header="cache-control: public, max-age=31536000, immutable" "../$SRC_TARBALL" s3://ziglang.org/builds/ +$S3CMD put -P --no-mime-magic "../$LANGREF" s3://ziglang.org/documentation/master/index.html --add-header="Cache-Control: max-age=0, must-revalidate" +$S3CMD put -P --no-mime-magic www/download/index.html s3://ziglang.org/download/index.html --add-header="Cache-Control: max-age=0, must-revalidate" +$S3CMD put -P --no-mime-magic www/download/index.json s3://ziglang.org/download/index.json --add-header="Cache-Control: max-age=0, must-revalidate" diff --git a/lib/std/array_list.zig b/lib/std/array_list.zig index 59fd2a10e5..26342d7833 100644 --- a/lib/std/array_list.zig +++ b/lib/std/array_list.zig @@ -58,7 +58,7 @@ pub fn AlignedArrayList(comptime T: type, comptime alignment: ?u29) type { return self.items[0..self.len]; } - /// Safely access index i of the list. + /// Safely access index i of the list. pub fn at(self: Self, i: usize) T { return self.toSliceConst()[i]; } diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index af045c5231..689c5cd898 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -144,6 +144,7 @@ pub const TypeInfo = union(enum) { alignment: comptime_int, child: type, is_allowzero: bool, + /// The type of the sentinel is the element type of the pointer, which is /// the value of the `child` field in this struct. However there is no way /// to refer to that type here, so we use `var`. @@ -164,6 +165,7 @@ pub const TypeInfo = union(enum) { pub const Array = struct { len: comptime_int, child: type, + /// The type of the sentinel is the element type of the array, which is /// the value of the `child` field in this struct. However there is no way /// to refer to that type here, so we use `var`. diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index 866b3ad67e..36621758b2 100644 --- a/lib/std/child_process.zig +++ b/lib/std/child_process.zig @@ -259,7 +259,7 @@ pub const ChildProcess = struct { } fn handleWaitResult(self: *ChildProcess, status: u32) void { - // TODO https://github.com/ziglang/zig/issues/3190 + // TODO https://github.com/ziglang/zig/issues/3190 var term = self.cleanupAfterWait(status); self.term = term; } diff --git a/lib/std/debug/failing_allocator.zig b/lib/std/debug/failing_allocator.zig index 6afd7c4880..081a29cd97 100644 --- a/lib/std/debug/failing_allocator.zig +++ b/lib/std/debug/failing_allocator.zig @@ -5,10 +5,10 @@ const mem = std.mem; /// memory conditions are handled correctly. /// /// To use this, first initialize it and get an allocator with -/// +/// /// `const failing_allocator = &FailingAllocator.init(<allocator>, /// <fail_index>).allocator;` -/// +/// /// Then use `failing_allocator` anywhere you would have used a /// different allocator. pub const FailingAllocator = struct { diff --git a/lib/std/event/fs.zig b/lib/std/event/fs.zig index 965302e1e6..a4b6488344 100644 --- a/lib/std/event/fs.zig +++ b/lib/std/event/fs.zig @@ -9,6 +9,12 @@ const windows = os.windows; const Loop = event.Loop; const fd_t = os.fd_t; const File = std.fs.File; +const Allocator = mem.Allocator; + +//! TODO mege this with `std.fs` + +const global_event_loop = Loop.instance orelse + @compileError("std.event.fs currently only works with event-based I/O"); pub const RequestNode = std.atomic.Queue(Request).Node; @@ -84,7 +90,7 @@ pub const Request = struct { pub const PWriteVError = error{OutOfMemory} || File.WriteError; /// data - just the inner references - must live until pwritev frame completes. -pub fn pwritev(loop: *Loop, fd: fd_t, data: []const []const u8, offset: usize) PWriteVError!void { +pub fn pwritev(allocator: *Allocator, fd: fd_t, data: []const []const u8, offset: usize) PWriteVError!void { switch (builtin.os) { .macosx, .linux, @@ -92,8 +98,8 @@ pub fn pwritev(loop: *Loop, fd: fd_t, data: []const []const u8, offset: usize) P .netbsd, .dragonfly, => { - const iovecs = try loop.allocator.alloc(os.iovec_const, data.len); - defer loop.allocator.free(iovecs); + const iovecs = try allocator.alloc(os.iovec_const, data.len); + defer allocator.free(iovecs); for (data) |buf, i| { iovecs[i] = os.iovec_const{ @@ -102,31 +108,31 @@ pub fn pwritev(loop: *Loop, fd: fd_t, data: []const []const u8, offset: usize) P }; } - return pwritevPosix(loop, fd, iovecs, offset); + return pwritevPosix(fd, iovecs, offset); }, .windows => { - const data_copy = try std.mem.dupe(loop.allocator, []const u8, data); - defer loop.allocator.free(data_copy); - return pwritevWindows(loop, fd, data, offset); + const data_copy = try std.mem.dupe(allocator, []const u8, data); + defer allocator.free(data_copy); + return pwritevWindows(fd, data, offset); }, else => @compileError("Unsupported OS"), } } /// data must outlive the returned frame -pub fn pwritevWindows(loop: *Loop, fd: fd_t, data: []const []const u8, offset: usize) os.WindowsWriteError!void { +pub fn pwritevWindows(fd: fd_t, data: []const []const u8, offset: usize) os.WindowsWriteError!void { if (data.len == 0) return; - if (data.len == 1) return pwriteWindows(loop, fd, data[0], offset); + if (data.len == 1) return pwriteWindows(fd, data[0], offset); // TODO do these in parallel var off = offset; for (data) |buf| { - try pwriteWindows(loop, fd, buf, off); + try pwriteWindows(fd, buf, off); off += buf.len; } } -pub fn pwriteWindows(loop: *Loop, fd: fd_t, data: []const u8, offset: u64) os.WindowsWriteError!void { +pub fn pwriteWindows(fd: fd_t, data: []const u8, offset: u64) os.WindowsWriteError!void { var resume_node = Loop.ResumeNode.Basic{ .base = Loop.ResumeNode{ .id = Loop.ResumeNode.Id.Basic, @@ -141,9 +147,9 @@ pub fn pwriteWindows(loop: *Loop, fd: fd_t, data: []const u8, offset: u64) os.Wi }, }; // TODO only call create io completion port once per fd - _ = windows.CreateIoCompletionPort(fd, loop.os_data.io_port, undefined, undefined); - loop.beginOneEvent(); - errdefer loop.finishOneEvent(); + _ = windows.CreateIoCompletionPort(fd, global_event_loop.os_data.io_port, undefined, undefined); + global_event_loop.beginOneEvent(); + errdefer global_event_loop.finishOneEvent(); errdefer { _ = windows.kernel32.CancelIoEx(fd, &resume_node.base.overlapped); @@ -166,12 +172,7 @@ pub fn pwriteWindows(loop: *Loop, fd: fd_t, data: []const u8, offset: u64) os.Wi } /// iovecs must live until pwritev frame completes. -pub fn pwritevPosix( - loop: *Loop, - fd: fd_t, - iovecs: []const os.iovec_const, - offset: usize, -) os.WriteError!void { +pub fn pwritevPosix(fd: fd_t, iovecs: []const os.iovec_const, offset: usize) os.WriteError!void { var req_node = RequestNode{ .prev = null, .next = null, @@ -194,21 +195,17 @@ pub fn pwritevPosix( }, }; - errdefer loop.posixFsCancel(&req_node); + errdefer global_event_loop.posixFsCancel(&req_node); suspend { - loop.posixFsRequest(&req_node); + global_event_loop.posixFsRequest(&req_node); } return req_node.data.msg.PWriteV.result; } /// iovecs must live until pwritev frame completes. -pub fn writevPosix( - loop: *Loop, - fd: fd_t, - iovecs: []const os.iovec_const, -) os.WriteError!void { +pub fn writevPosix(fd: fd_t, iovecs: []const os.iovec_const) os.WriteError!void { var req_node = RequestNode{ .prev = null, .next = null, @@ -231,7 +228,7 @@ pub fn writevPosix( }; suspend { - loop.posixFsRequest(&req_node); + global_event_loop.posixFsRequest(&req_node); } return req_node.data.msg.WriteV.result; @@ -240,7 +237,7 @@ pub fn writevPosix( pub const PReadVError = error{OutOfMemory} || File.ReadError; /// data - just the inner references - must live until preadv frame completes. -pub fn preadv(loop: *Loop, fd: fd_t, data: []const []u8, offset: usize) PReadVError!usize { +pub fn preadv(allocator: *Allocator, fd: fd_t, data: []const []u8, offset: usize) PReadVError!usize { assert(data.len != 0); switch (builtin.os) { .macosx, @@ -249,8 +246,8 @@ pub fn preadv(loop: *Loop, fd: fd_t, data: []const []u8, offset: usize) PReadVEr .netbsd, .dragonfly, => { - const iovecs = try loop.allocator.alloc(os.iovec, data.len); - defer loop.allocator.free(iovecs); + const iovecs = try allocator.alloc(os.iovec, data.len); + defer allocator.free(iovecs); for (data) |buf, i| { iovecs[i] = os.iovec{ @@ -259,21 +256,21 @@ pub fn preadv(loop: *Loop, fd: fd_t, data: []const []u8, offset: usize) PReadVEr }; } - return preadvPosix(loop, fd, iovecs, offset); + return preadvPosix(fd, iovecs, offset); }, .windows => { - const data_copy = try std.mem.dupe(loop.allocator, []u8, data); - defer loop.allocator.free(data_copy); - return preadvWindows(loop, fd, data_copy, offset); + const data_copy = try std.mem.dupe(allocator, []u8, data); + defer allocator.free(data_copy); + return preadvWindows(fd, data_copy, offset); }, else => @compileError("Unsupported OS"), } } /// data must outlive the returned frame -pub fn preadvWindows(loop: *Loop, fd: fd_t, data: []const []u8, offset: u64) !usize { +pub fn preadvWindows(fd: fd_t, data: []const []u8, offset: u64) !usize { assert(data.len != 0); - if (data.len == 1) return preadWindows(loop, fd, data[0], offset); + if (data.len == 1) return preadWindows(fd, data[0], offset); // TODO do these in parallel? var off: usize = 0; @@ -281,7 +278,7 @@ pub fn preadvWindows(loop: *Loop, fd: fd_t, data: []const []u8, offset: u64) !us var inner_off: usize = 0; while (true) { const v = data[iov_i]; - const amt_read = try preadWindows(loop, fd, v[inner_off .. v.len - inner_off], offset + off); + const amt_read = try preadWindows(fd, v[inner_off .. v.len - inner_off], offset + off); off += amt_read; inner_off += amt_read; if (inner_off == v.len) { @@ -295,7 +292,7 @@ pub fn preadvWindows(loop: *Loop, fd: fd_t, data: []const []u8, offset: u64) !us } } -pub fn preadWindows(loop: *Loop, fd: fd_t, data: []u8, offset: u64) !usize { +pub fn preadWindows(fd: fd_t, data: []u8, offset: u64) !usize { var resume_node = Loop.ResumeNode.Basic{ .base = Loop.ResumeNode{ .id = Loop.ResumeNode.Id.Basic, @@ -310,9 +307,9 @@ pub fn preadWindows(loop: *Loop, fd: fd_t, data: []u8, offset: u64) !usize { }, }; // TODO only call create io completion port once per fd - _ = windows.CreateIoCompletionPort(fd, loop.os_data.io_port, undefined, undefined) catch undefined; - loop.beginOneEvent(); - errdefer loop.finishOneEvent(); + _ = windows.CreateIoCompletionPort(fd, global_event_loop.os_data.io_port, undefined, undefined) catch undefined; + global_event_loop.beginOneEvent(); + errdefer global_event_loop.finishOneEvent(); errdefer { _ = windows.kernel32.CancelIoEx(fd, &resume_node.base.overlapped); @@ -334,12 +331,7 @@ pub fn preadWindows(loop: *Loop, fd: fd_t, data: []u8, offset: u64) !usize { } /// iovecs must live until preadv frame completes -pub fn preadvPosix( - loop: *Loop, - fd: fd_t, - iovecs: []const os.iovec, - offset: usize, -) os.ReadError!usize { +pub fn preadvPosix(fd: fd_t, iovecs: []const os.iovec, offset: usize) os.ReadError!usize { var req_node = RequestNode{ .prev = null, .next = null, @@ -362,21 +354,16 @@ pub fn preadvPosix( }, }; - errdefer loop.posixFsCancel(&req_node); + errdefer global_event_loop.posixFsCancel(&req_node); suspend { - loop.posixFsRequest(&req_node); + global_event_loop.posixFsRequest(&req_node); } return req_node.data.msg.PReadV.result; } -pub fn openPosix( - loop: *Loop, - path: []const u8, - flags: u32, - mode: File.Mode, -) File.OpenError!fd_t { +pub fn openPosix(path: []const u8, flags: u32, mode: File.Mode) File.OpenError!fd_t { const path_c = try std.os.toPosixPath(path); var req_node = RequestNode{ @@ -401,21 +388,21 @@ pub fn openPosix( }, }; - errdefer loop.posixFsCancel(&req_node); + errdefer global_event_loop.posixFsCancel(&req_node); suspend { - loop.posixFsRequest(&req_node); + global_event_loop.posixFsRequest(&req_node); } return req_node.data.msg.Open.result; } -pub fn openRead(loop: *Loop, path: []const u8) File.OpenError!fd_t { +pub fn openRead(path: []const u8) File.OpenError!fd_t { switch (builtin.os) { .macosx, .linux, .freebsd, .netbsd, .dragonfly => { const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0; const flags = O_LARGEFILE | os.O_RDONLY | os.O_CLOEXEC; - return openPosix(loop, path, flags, File.default_mode); + return openPosix(path, flags, File.default_mode); }, .windows => return windows.CreateFile( @@ -434,12 +421,12 @@ pub fn openRead(loop: *Loop, path: []const u8) File.OpenError!fd_t { /// Creates if does not exist. Truncates the file if it exists. /// Uses the default mode. -pub fn openWrite(loop: *Loop, path: []const u8) File.OpenError!fd_t { - return openWriteMode(loop, path, File.default_mode); +pub fn openWrite(path: []const u8) File.OpenError!fd_t { + return openWriteMode(path, File.default_mode); } /// Creates if does not exist. Truncates the file if it exists. -pub fn openWriteMode(loop: *Loop, path: []const u8, mode: File.Mode) File.OpenError!fd_t { +pub fn openWriteMode(path: []const u8, mode: File.Mode) File.OpenError!fd_t { switch (builtin.os) { .macosx, .linux, @@ -449,7 +436,7 @@ pub fn openWriteMode(loop: *Loop, path: []const u8, mode: File.Mode) File.OpenEr => { const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0; const flags = O_LARGEFILE | os.O_WRONLY | os.O_CREAT | os.O_CLOEXEC | os.O_TRUNC; - return openPosix(loop, path, flags, File.default_mode); + return openPosix(path, flags, File.default_mode); }, .windows => return windows.CreateFile( path, @@ -465,16 +452,12 @@ pub fn openWriteMode(loop: *Loop, path: []const u8, mode: File.Mode) File.OpenEr } /// Creates if does not exist. Does not truncate. -pub fn openReadWrite( - loop: *Loop, - path: []const u8, - mode: File.Mode, -) File.OpenError!fd_t { +pub fn openReadWrite(path: []const u8, mode: File.Mode) File.OpenError!fd_t { switch (builtin.os) { .macosx, .linux, .freebsd, .netbsd, .dragonfly => { const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0; const flags = O_LARGEFILE | os.O_RDWR | os.O_CREAT | os.O_CLOEXEC; - return openPosix(loop, path, flags, mode); + return openPosix(path, flags, mode); }, .windows => return windows.CreateFile( @@ -498,7 +481,7 @@ pub fn openReadWrite( /// If you call `setHandle` then finishing will close the fd; otherwise finishing /// will deallocate the `CloseOperation`. pub const CloseOperation = struct { - loop: *Loop, + allocator: *Allocator, os_data: OsData, const OsData = switch (builtin.os) { @@ -516,10 +499,10 @@ pub const CloseOperation = struct { close_req_node: RequestNode, }; - pub fn start(loop: *Loop) (error{OutOfMemory}!*CloseOperation) { - const self = try loop.allocator.create(CloseOperation); + pub fn start(allocator: *Allocator) (error{OutOfMemory}!*CloseOperation) { + const self = try allocator.create(CloseOperation); self.* = CloseOperation{ - .loop = loop, + .allocator = allocator, .os_data = switch (builtin.os) { .linux, .macosx, .freebsd, .netbsd, .dragonfly => initOsDataPosix(self), .windows => OsData{ .handle = null }, @@ -555,16 +538,16 @@ pub const CloseOperation = struct { .dragonfly, => { if (self.os_data.have_fd) { - self.loop.posixFsRequest(&self.os_data.close_req_node); + global_event_loop.posixFsRequest(&self.os_data.close_req_node); } else { - self.loop.allocator.destroy(self); + self.allocator.destroy(self); } }, .windows => { if (self.os_data.handle) |handle| { os.close(handle); } - self.loop.allocator.destroy(self); + self.allocator.destroy(self); }, else => @compileError("Unsupported OS"), } @@ -627,25 +610,25 @@ pub const CloseOperation = struct { /// contents must remain alive until writeFile completes. /// TODO make this atomic or provide writeFileAtomic and rename this one to writeFileTruncate -pub fn writeFile(loop: *Loop, path: []const u8, contents: []const u8) !void { - return writeFileMode(loop, path, contents, File.default_mode); +pub fn writeFile(allocator: *Allocator, path: []const u8, contents: []const u8) !void { + return writeFileMode(allocator, path, contents, File.default_mode); } /// contents must remain alive until writeFile completes. -pub fn writeFileMode(loop: *Loop, path: []const u8, contents: []const u8, mode: File.Mode) !void { +pub fn writeFileMode(allocator: *Allocator, path: []const u8, contents: []const u8, mode: File.Mode) !void { switch (builtin.os) { .linux, .macosx, .freebsd, .netbsd, .dragonfly, - => return writeFileModeThread(loop, path, contents, mode), - .windows => return writeFileWindows(loop, path, contents), + => return writeFileModeThread(allocator, path, contents, mode), + .windows => return writeFileWindows(path, contents), else => @compileError("Unsupported OS"), } } -fn writeFileWindows(loop: *Loop, path: []const u8, contents: []const u8) !void { +fn writeFileWindows(path: []const u8, contents: []const u8) !void { const handle = try windows.CreateFile( path, windows.GENERIC_WRITE, @@ -657,12 +640,12 @@ fn writeFileWindows(loop: *Loop, path: []const u8, contents: []const u8) !void { ); defer os.close(handle); - try pwriteWindows(loop, handle, contents, 0); + try pwriteWindows(handle, contents, 0); } -fn writeFileModeThread(loop: *Loop, path: []const u8, contents: []const u8, mode: File.Mode) !void { - const path_with_null = try std.cstr.addNullByte(loop.allocator, path); - defer loop.allocator.free(path_with_null); +fn writeFileModeThread(allocator: *Allocator, path: []const u8, contents: []const u8, mode: File.Mode) !void { + const path_with_null = try std.cstr.addNullByte(allocator, path); + defer allocator.free(path_with_null); var req_node = RequestNode{ .prev = null, @@ -686,10 +669,10 @@ fn writeFileModeThread(loop: *Loop, path: []const u8, contents: []const u8, mode }, }; - errdefer loop.posixFsCancel(&req_node); + errdefer global_event_loop.posixFsCancel(&req_node); suspend { - loop.posixFsRequest(&req_node); + global_event_loop.posixFsRequest(&req_node); } return req_node.data.msg.WriteFile.result; @@ -698,21 +681,21 @@ fn writeFileModeThread(loop: *Loop, path: []const u8, contents: []const u8, mode /// The frame resumes when the last data has been confirmed written, but before the file handle /// is closed. /// Caller owns returned memory. -pub fn readFile(loop: *Loop, file_path: []const u8, max_size: usize) ![]u8 { - var close_op = try CloseOperation.start(loop); +pub fn readFile(allocator: *Allocator, file_path: []const u8, max_size: usize) ![]u8 { + var close_op = try CloseOperation.start(allocator); defer close_op.finish(); - const fd = try openRead(loop, file_path); + const fd = try openRead(file_path); close_op.setHandle(fd); - var list = std.ArrayList(u8).init(loop.allocator); + var list = std.ArrayList(u8).init(allocator); defer list.deinit(); while (true) { try list.ensureCapacity(list.len + mem.page_size); const buf = list.items[list.len..]; const buf_array = [_][]u8{buf}; - const amt = try preadv(loop, fd, buf_array, list.len); + const amt = try preadv(allocator, fd, buf_array, list.len); list.len += amt; if (list.len > max_size) { return error.FileTooBig; @@ -738,610 +721,603 @@ fn hashString(s: []const u16) u32 { return @truncate(u32, std.hash.Wyhash.hash(0, @sliceToBytes(s))); } -//pub const WatchEventError = error{ -// UserResourceLimitReached, -// SystemResources, -// AccessDenied, -// Unexpected, // TODO remove this possibility -//}; -// -//pub fn Watch(comptime V: type) type { -// return struct { -// channel: *event.Channel(Event.Error!Event), -// os_data: OsData, -// -// const OsData = switch (builtin.os) { -// .macosx, .freebsd, .netbsd, .dragonfly => struct { -// file_table: FileTable, -// table_lock: event.Lock, -// -// const FileTable = std.StringHashmap(*Put); -// const Put = struct { -// putter: anyframe, -// value_ptr: *V, -// }; -// }, -// -// .linux => LinuxOsData, -// .windows => WindowsOsData, -// -// else => @compileError("Unsupported OS"), -// }; -// -// const WindowsOsData = struct { -// table_lock: event.Lock, -// dir_table: DirTable, -// all_putters: std.atomic.Queue(anyframe), -// ref_count: std.atomic.Int(usize), -// -// const DirTable = std.StringHashMap(*Dir); -// const FileTable = std.HashMap([]const u16, V, hashString, eqlString); -// -// const Dir = struct { -// putter: anyframe, -// file_table: FileTable, -// table_lock: event.Lock, -// }; -// }; -// -// const LinuxOsData = struct { -// putter: anyframe, -// inotify_fd: i32, -// wd_table: WdTable, -// table_lock: event.Lock, -// -// const WdTable = std.AutoHashMap(i32, Dir); -// const FileTable = std.StringHashMap(V); -// -// const Dir = struct { -// dirname: []const u8, -// file_table: FileTable, -// }; -// }; -// -// const FileToHandle = std.StringHashMap(anyframe); -// -// const Self = @This(); -// -// pub const Event = struct { -// id: Id, -// data: V, -// -// pub const Id = WatchEventId; -// pub const Error = WatchEventError; -// }; -// -// pub fn create(loop: *Loop, event_buf_count: usize) !*Self { -// const channel = try event.Channel(Self.Event.Error!Self.Event).create(loop, event_buf_count); -// errdefer channel.destroy(); -// -// switch (builtin.os) { -// .linux => { -// const inotify_fd = try os.inotify_init1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC); -// errdefer os.close(inotify_fd); -// -// var result: *Self = undefined; -// _ = try async<loop.allocator> linuxEventPutter(inotify_fd, channel, &result); -// return result; -// }, -// -// .windows => { -// const self = try loop.allocator.create(Self); -// errdefer loop.allocator.destroy(self); -// self.* = Self{ -// .channel = channel, -// .os_data = OsData{ -// .table_lock = event.Lock.init(loop), -// .dir_table = OsData.DirTable.init(loop.allocator), -// .ref_count = std.atomic.Int(usize).init(1), -// .all_putters = std.atomic.Queue(anyframe).init(), -// }, -// }; -// return self; -// }, -// -// .macosx, .freebsd, .netbsd, .dragonfly => { -// const self = try loop.allocator.create(Self); -// errdefer loop.allocator.destroy(self); -// -// self.* = Self{ -// .channel = channel, -// .os_data = OsData{ -// .table_lock = event.Lock.init(loop), -// .file_table = OsData.FileTable.init(loop.allocator), -// }, -// }; -// return self; -// }, -// else => @compileError("Unsupported OS"), -// } -// } -// -// /// All addFile calls and removeFile calls must have completed. -// pub fn destroy(self: *Self) void { -// switch (builtin.os) { -// .macosx, .freebsd, .netbsd, .dragonfly => { -// // TODO we need to cancel the frames before destroying the lock -// self.os_data.table_lock.deinit(); -// var it = self.os_data.file_table.iterator(); -// while (it.next()) |entry| { -// cancel entry.value.putter; -// self.channel.loop.allocator.free(entry.key); -// } -// self.channel.destroy(); -// }, -// .linux => cancel self.os_data.putter, -// .windows => { -// while (self.os_data.all_putters.get()) |putter_node| { -// cancel putter_node.data; -// } -// self.deref(); -// }, -// else => @compileError("Unsupported OS"), -// } -// } -// -// fn ref(self: *Self) void { -// _ = self.os_data.ref_count.incr(); -// } -// -// fn deref(self: *Self) void { -// if (self.os_data.ref_count.decr() == 1) { -// const allocator = self.channel.loop.allocator; -// self.os_data.table_lock.deinit(); -// var it = self.os_data.dir_table.iterator(); -// while (it.next()) |entry| { -// allocator.free(entry.key); -// allocator.destroy(entry.value); -// } -// self.os_data.dir_table.deinit(); -// self.channel.destroy(); -// allocator.destroy(self); -// } -// } -// -// pub async fn addFile(self: *Self, file_path: []const u8, value: V) !?V { -// switch (builtin.os) { -// .macosx, .freebsd, .netbsd, .dragonfly => return await (async addFileKEvent(self, file_path, value) catch unreachable), -// .linux => return await (async addFileLinux(self, file_path, value) catch unreachable), -// .windows => return await (async addFileWindows(self, file_path, value) catch unreachable), -// else => @compileError("Unsupported OS"), -// } -// } -// -// async fn addFileKEvent(self: *Self, file_path: []const u8, value: V) !?V { -// const resolved_path = try std.fs.path.resolve(self.channel.loop.allocator, [_][]const u8{file_path}); -// var resolved_path_consumed = false; -// defer if (!resolved_path_consumed) self.channel.loop.allocator.free(resolved_path); -// -// var close_op = try CloseOperation.start(self.channel.loop); -// var close_op_consumed = false; -// defer if (!close_op_consumed) close_op.finish(); -// -// const flags = if (comptime std.Target.current.isDarwin()) os.O_SYMLINK | os.O_EVTONLY else 0; -// const mode = 0; -// const fd = try await (async openPosix(self.channel.loop, resolved_path, flags, mode) catch unreachable); -// close_op.setHandle(fd); -// -// var put_data: *OsData.Put = undefined; -// const putter = try async self.kqPutEvents(close_op, value, &put_data); -// close_op_consumed = true; -// errdefer cancel putter; -// -// const result = blk: { -// const held = await (async self.os_data.table_lock.acquire() catch unreachable); -// defer held.release(); -// -// const gop = try self.os_data.file_table.getOrPut(resolved_path); -// if (gop.found_existing) { -// const prev_value = gop.kv.value.value_ptr.*; -// cancel gop.kv.value.putter; -// gop.kv.value = put_data; -// break :blk prev_value; -// } else { -// resolved_path_consumed = true; -// gop.kv.value = put_data; -// break :blk null; -// } -// }; -// -// return result; -// } -// -// async fn kqPutEvents(self: *Self, close_op: *CloseOperation, value: V, out_put: **OsData.Put) void { -// var value_copy = value; -// var put = OsData.Put{ -// .putter = @frame(), -// .value_ptr = &value_copy, -// }; -// out_put.* = &put; -// self.channel.loop.beginOneEvent(); -// -// defer { -// close_op.finish(); -// self.channel.loop.finishOneEvent(); -// } -// -// while (true) { -// if (await (async self.channel.loop.bsdWaitKev( -// @intCast(usize, close_op.getHandle()), -// os.EVFILT_VNODE, -// os.NOTE_WRITE | os.NOTE_DELETE, -// ) catch unreachable)) |kev| { -// // TODO handle EV_ERROR -// if (kev.fflags & os.NOTE_DELETE != 0) { -// await (async self.channel.put(Self.Event{ -// .id = Event.Id.Delete, -// .data = value_copy, -// }) catch unreachable); -// } else if (kev.fflags & os.NOTE_WRITE != 0) { -// await (async self.channel.put(Self.Event{ -// .id = Event.Id.CloseWrite, -// .data = value_copy, -// }) catch unreachable); -// } -// } else |err| switch (err) { -// error.EventNotFound => unreachable, -// error.ProcessNotFound => unreachable, -// error.Overflow => unreachable, -// error.AccessDenied, error.SystemResources => |casted_err| { -// await (async self.channel.put(casted_err) catch unreachable); -// }, -// } -// } -// } -// -// async fn addFileLinux(self: *Self, file_path: []const u8, value: V) !?V { -// const value_copy = value; -// -// const dirname = std.fs.path.dirname(file_path) orelse "."; -// const dirname_with_null = try std.cstr.addNullByte(self.channel.loop.allocator, dirname); -// var dirname_with_null_consumed = false; -// defer if (!dirname_with_null_consumed) self.channel.loop.allocator.free(dirname_with_null); -// -// const basename = std.fs.path.basename(file_path); -// const basename_with_null = try std.cstr.addNullByte(self.channel.loop.allocator, basename); -// var basename_with_null_consumed = false; -// defer if (!basename_with_null_consumed) self.channel.loop.allocator.free(basename_with_null); -// -// const wd = try os.inotify_add_watchC( -// self.os_data.inotify_fd, -// dirname_with_null.ptr, -// os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_EXCL_UNLINK, -// ); -// // wd is either a newly created watch or an existing one. -// -// const held = await (async self.os_data.table_lock.acquire() catch unreachable); -// defer held.release(); -// -// const gop = try self.os_data.wd_table.getOrPut(wd); -// if (!gop.found_existing) { -// gop.kv.value = OsData.Dir{ -// .dirname = dirname_with_null, -// .file_table = OsData.FileTable.init(self.channel.loop.allocator), -// }; -// dirname_with_null_consumed = true; -// } -// const dir = &gop.kv.value; -// -// const file_table_gop = try dir.file_table.getOrPut(basename_with_null); -// if (file_table_gop.found_existing) { -// const prev_value = file_table_gop.kv.value; -// file_table_gop.kv.value = value_copy; -// return prev_value; -// } else { -// file_table_gop.kv.value = value_copy; -// basename_with_null_consumed = true; -// return null; -// } -// } -// -// async fn addFileWindows(self: *Self, file_path: []const u8, value: V) !?V { -// const value_copy = value; -// // TODO we might need to convert dirname and basename to canonical file paths ("short"?) -// -// const dirname = try std.mem.dupe(self.channel.loop.allocator, u8, std.fs.path.dirname(file_path) orelse "."); -// var dirname_consumed = false; -// defer if (!dirname_consumed) self.channel.loop.allocator.free(dirname); -// -// const dirname_utf16le = try std.unicode.utf8ToUtf16LeWithNull(self.channel.loop.allocator, dirname); -// defer self.channel.loop.allocator.free(dirname_utf16le); -// -// // TODO https://github.com/ziglang/zig/issues/265 -// const basename = std.fs.path.basename(file_path); -// const basename_utf16le_null = try std.unicode.utf8ToUtf16LeWithNull(self.channel.loop.allocator, basename); -// var basename_utf16le_null_consumed = false; -// defer if (!basename_utf16le_null_consumed) self.channel.loop.allocator.free(basename_utf16le_null); -// const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1]; -// -// const dir_handle = try windows.CreateFileW( -// dirname_utf16le.ptr, -// windows.FILE_LIST_DIRECTORY, -// windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE | windows.FILE_SHARE_WRITE, -// null, -// windows.OPEN_EXISTING, -// windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OVERLAPPED, -// null, -// ); -// var dir_handle_consumed = false; -// defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle); -// -// const held = await (async self.os_data.table_lock.acquire() catch unreachable); -// defer held.release(); -// -// const gop = try self.os_data.dir_table.getOrPut(dirname); -// if (gop.found_existing) { -// const dir = gop.kv.value; -// const held_dir_lock = await (async dir.table_lock.acquire() catch unreachable); -// defer held_dir_lock.release(); -// -// const file_gop = try dir.file_table.getOrPut(basename_utf16le_no_null); -// if (file_gop.found_existing) { -// const prev_value = file_gop.kv.value; -// file_gop.kv.value = value_copy; -// return prev_value; -// } else { -// file_gop.kv.value = value_copy; -// basename_utf16le_null_consumed = true; -// return null; -// } -// } else { -// errdefer _ = self.os_data.dir_table.remove(dirname); -// const dir = try self.channel.loop.allocator.create(OsData.Dir); -// errdefer self.channel.loop.allocator.destroy(dir); -// -// dir.* = OsData.Dir{ -// .file_table = OsData.FileTable.init(self.channel.loop.allocator), -// .table_lock = event.Lock.init(self.channel.loop), -// .putter = undefined, -// }; -// gop.kv.value = dir; -// assert((try dir.file_table.put(basename_utf16le_no_null, value_copy)) == null); -// basename_utf16le_null_consumed = true; -// -// dir.putter = try async self.windowsDirReader(dir_handle, dir); -// dir_handle_consumed = true; -// -// dirname_consumed = true; -// -// return null; -// } -// } -// -// async fn windowsDirReader(self: *Self, dir_handle: windows.HANDLE, dir: *OsData.Dir) void { -// self.ref(); -// defer self.deref(); -// -// defer os.close(dir_handle); -// -// var putter_node = std.atomic.Queue(anyframe).Node{ -// .data = @frame(), -// .prev = null, -// .next = null, -// }; -// self.os_data.all_putters.put(&putter_node); -// defer _ = self.os_data.all_putters.remove(&putter_node); -// -// var resume_node = Loop.ResumeNode.Basic{ -// .base = Loop.ResumeNode{ -// .id = Loop.ResumeNode.Id.Basic, -// .handle = @frame(), -// .overlapped = windows.OVERLAPPED{ -// .Internal = 0, -// .InternalHigh = 0, -// .Offset = 0, -// .OffsetHigh = 0, -// .hEvent = null, -// }, -// }, -// }; -// var event_buf: [4096]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined; -// -// // TODO handle this error not in the channel but in the setup -// _ = windows.CreateIoCompletionPort( -// dir_handle, -// self.channel.loop.os_data.io_port, -// undefined, -// undefined, -// ) catch |err| { -// await (async self.channel.put(err) catch unreachable); -// return; -// }; -// -// while (true) { -// { -// // TODO only 1 beginOneEvent for the whole function -// self.channel.loop.beginOneEvent(); -// errdefer self.channel.loop.finishOneEvent(); -// errdefer { -// _ = windows.kernel32.CancelIoEx(dir_handle, &resume_node.base.overlapped); -// } -// suspend { -// _ = windows.kernel32.ReadDirectoryChangesW( -// dir_handle, -// &event_buf, -// @intCast(windows.DWORD, event_buf.len), -// windows.FALSE, // watch subtree -// windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME | -// windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE | -// windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS | -// windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY, -// null, // number of bytes transferred (unused for async) -// &resume_node.base.overlapped, -// null, // completion routine - unused because we use IOCP -// ); -// } -// } -// var bytes_transferred: windows.DWORD = undefined; -// if (windows.kernel32.GetOverlappedResult(dir_handle, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) { -// const err = switch (windows.kernel32.GetLastError()) { -// else => |err| windows.unexpectedError(err), -// }; -// await (async self.channel.put(err) catch unreachable); -// } else { -// // can't use @bytesToSlice because of the special variable length name field -// var ptr = event_buf[0..].ptr; -// const end_ptr = ptr + bytes_transferred; -// var ev: *windows.FILE_NOTIFY_INFORMATION = undefined; -// while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += ev.NextEntryOffset) { -// ev = @ptrCast(*windows.FILE_NOTIFY_INFORMATION, ptr); -// const emit = switch (ev.Action) { -// windows.FILE_ACTION_REMOVED => WatchEventId.Delete, -// windows.FILE_ACTION_MODIFIED => WatchEventId.CloseWrite, -// else => null, -// }; -// if (emit) |id| { -// const basename_utf16le = ([*]u16)(&ev.FileName)[0 .. ev.FileNameLength / 2]; -// const user_value = blk: { -// const held = await (async dir.table_lock.acquire() catch unreachable); -// defer held.release(); -// -// if (dir.file_table.get(basename_utf16le)) |entry| { -// break :blk entry.value; -// } else { -// break :blk null; -// } -// }; -// if (user_value) |v| { -// await (async self.channel.put(Event{ -// .id = id, -// .data = v, -// }) catch unreachable); -// } -// } -// if (ev.NextEntryOffset == 0) break; -// } -// } -// } -// } -// -// pub async fn removeFile(self: *Self, file_path: []const u8) ?V { -// @panic("TODO"); -// } -// -// async fn linuxEventPutter(inotify_fd: i32, channel: *event.Channel(Event.Error!Event), out_watch: **Self) void { -// const loop = channel.loop; -// -// var watch = Self{ -// .channel = channel, -// .os_data = OsData{ -// .putter = @frame(), -// .inotify_fd = inotify_fd, -// .wd_table = OsData.WdTable.init(loop.allocator), -// .table_lock = event.Lock.init(loop), -// }, -// }; -// out_watch.* = &watch; -// -// loop.beginOneEvent(); -// -// defer { -// watch.os_data.table_lock.deinit(); -// var wd_it = watch.os_data.wd_table.iterator(); -// while (wd_it.next()) |wd_entry| { -// var file_it = wd_entry.value.file_table.iterator(); -// while (file_it.next()) |file_entry| { -// loop.allocator.free(file_entry.key); -// } -// loop.allocator.free(wd_entry.value.dirname); -// } -// loop.finishOneEvent(); -// os.close(inotify_fd); -// channel.destroy(); -// } -// -// var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined; -// -// while (true) { -// const rc = os.linux.read(inotify_fd, &event_buf, event_buf.len); -// const errno = os.linux.getErrno(rc); -// switch (errno) { -// 0 => { -// // can't use @bytesToSlice because of the special variable length name field -// var ptr = event_buf[0..].ptr; -// const end_ptr = ptr + event_buf.len; -// var ev: *os.linux.inotify_event = undefined; -// while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += @sizeOf(os.linux.inotify_event) + ev.len) { -// ev = @ptrCast(*os.linux.inotify_event, ptr); -// if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) { -// const basename_ptr = ptr + @sizeOf(os.linux.inotify_event); -// const basename_with_null = basename_ptr[0 .. std.mem.len(u8, basename_ptr) + 1]; -// const user_value = blk: { -// const held = await (async watch.os_data.table_lock.acquire() catch unreachable); -// defer held.release(); -// -// const dir = &watch.os_data.wd_table.get(ev.wd).?.value; -// if (dir.file_table.get(basename_with_null)) |entry| { -// break :blk entry.value; -// } else { -// break :blk null; -// } -// }; -// if (user_value) |v| { -// await (async channel.put(Event{ -// .id = WatchEventId.CloseWrite, -// .data = v, -// }) catch unreachable); -// } -// } -// } -// }, -// os.linux.EINTR => continue, -// os.linux.EINVAL => unreachable, -// os.linux.EFAULT => unreachable, -// os.linux.EAGAIN => { -// (await (async loop.linuxWaitFd( -// inotify_fd, -// os.linux.EPOLLET | os.linux.EPOLLIN, -// ) catch unreachable)) catch |err| { -// const transformed_err = switch (err) { -// error.FileDescriptorAlreadyPresentInSet => unreachable, -// error.OperationCausesCircularLoop => unreachable, -// error.FileDescriptorNotRegistered => unreachable, -// error.FileDescriptorIncompatibleWithEpoll => unreachable, -// error.Unexpected => unreachable, -// else => |e| e, -// }; -// await (async channel.put(transformed_err) catch unreachable); -// }; -// }, -// else => unreachable, -// } -// } -// } -// }; -//} +pub const WatchEventError = error{ + UserResourceLimitReached, + SystemResources, + AccessDenied, + Unexpected, // TODO remove this possibility +}; + +pub fn Watch(comptime V: type) type { + return struct { + channel: *event.Channel(Event.Error!Event), + os_data: OsData, + allocator: *Allocator, + + const OsData = switch (builtin.os) { + .macosx, .freebsd, .netbsd, .dragonfly => struct { + file_table: FileTable, + table_lock: event.Lock, + + const FileTable = std.StringHashMap(*Put); + const Put = struct { + putter_frame: @Frame(kqPutEvents), + cancelled: bool = false, + value: V, + }; + }, + + .linux => LinuxOsData, + .windows => WindowsOsData, + + else => @compileError("Unsupported OS"), + }; + + const WindowsOsData = struct { + table_lock: event.Lock, + dir_table: DirTable, + all_putters: std.atomic.Queue(Put), + ref_count: std.atomic.Int(usize), + + const Put = struct { + putter: anyframe, + cancelled: bool = false, + }; + + const DirTable = std.StringHashMap(*Dir); + const FileTable = std.HashMap([]const u16, V, hashString, eqlString); + + const Dir = struct { + putter_frame: @Frame(windowsDirReader), + file_table: FileTable, + table_lock: event.Lock, + }; + }; + + const LinuxOsData = struct { + putter_frame: @Frame(linuxEventPutter), + inotify_fd: i32, + wd_table: WdTable, + table_lock: event.Lock, + cancelled: bool = false, + + const WdTable = std.AutoHashMap(i32, Dir); + const FileTable = std.StringHashMap(V); + + const Dir = struct { + dirname: []const u8, + file_table: FileTable, + }; + }; + + const Self = @This(); + + pub const Event = struct { + id: Id, + data: V, + + pub const Id = WatchEventId; + pub const Error = WatchEventError; + }; + + pub fn init(allocator: *Allocator, event_buf_count: usize) !*Self { + const channel = try allocator.create(event.Channel(Event.Error!Event)); + errdefer allocator.destroy(channel); + var buf = try allocator.alloc(Event.Error!Event, event_buf_count); + errdefer allocator.free(buf); + channel.init(buf); + errdefer channel.deinit(); + + const self = try allocator.create(Self); + errdefer allocator.destroy(self); + + switch (builtin.os) { + .linux => { + const inotify_fd = try os.inotify_init1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC); + errdefer os.close(inotify_fd); + + self.* = Self{ + .allocator = allocator, + .channel = channel, + .os_data = OsData{ + .putter_frame = undefined, + .inotify_fd = inotify_fd, + .wd_table = OsData.WdTable.init(allocator), + .table_lock = event.Lock.init(), + }, + }; + + self.os_data.putter_frame = async self.linuxEventPutter(); + return self; + }, + + .windows => { + self.* = Self{ + .allocator = allocator, + .channel = channel, + .os_data = OsData{ + .table_lock = event.Lock.init(), + .dir_table = OsData.DirTable.init(allocator), + .ref_count = std.atomic.Int(usize).init(1), + .all_putters = std.atomic.Queue(anyframe).init(), + }, + }; + return self; + }, + + .macosx, .freebsd, .netbsd, .dragonfly => { + self.* = Self{ + .allocator = allocator, + .channel = channel, + .os_data = OsData{ + .table_lock = event.Lock.init(), + .file_table = OsData.FileTable.init(allocator), + }, + }; + return self; + }, + else => @compileError("Unsupported OS"), + } + } + + /// All addFile calls and removeFile calls must have completed. + pub fn deinit(self: *Self) void { + switch (builtin.os) { + .macosx, .freebsd, .netbsd, .dragonfly => { + // TODO we need to cancel the frames before destroying the lock + self.os_data.table_lock.deinit(); + var it = self.os_data.file_table.iterator(); + while (it.next()) |entry| { + entry.cancelled = true; + await entry.value.putter; + self.allocator.free(entry.key); + self.allocator.free(entry.value); + } + self.channel.deinit(); + self.allocator.destroy(self.channel.buffer_nodes); + self.allocator.destroy(self); + }, + .linux => { + self.os_data.cancelled = true; + await self.os_data.putter_frame; + self.allocator.destroy(self); + }, + .windows => { + while (self.os_data.all_putters.get()) |putter_node| { + putter_node.cancelled = true; + await putter_node.frame; + } + self.deref(); + }, + else => @compileError("Unsupported OS"), + } + } + + fn ref(self: *Self) void { + _ = self.os_data.ref_count.incr(); + } + + fn deref(self: *Self) void { + if (self.os_data.ref_count.decr() == 1) { + self.os_data.table_lock.deinit(); + var it = self.os_data.dir_table.iterator(); + while (it.next()) |entry| { + self.allocator.free(entry.key); + self.allocator.destroy(entry.value); + } + self.os_data.dir_table.deinit(); + self.channel.deinit(); + self.allocator.destroy(self.channel.buffer_nodes); + self.allocator.destroy(self); + } + } + + pub fn addFile(self: *Self, file_path: []const u8, value: V) !?V { + switch (builtin.os) { + .macosx, .freebsd, .netbsd, .dragonfly => return addFileKEvent(self, file_path, value), + .linux => return addFileLinux(self, file_path, value), + .windows => return addFileWindows(self, file_path, value), + else => @compileError("Unsupported OS"), + } + } + + fn addFileKEvent(self: *Self, file_path: []const u8, value: V) !?V { + const resolved_path = try std.fs.path.resolve(self.allocator, [_][]const u8{file_path}); + var resolved_path_consumed = false; + defer if (!resolved_path_consumed) self.allocator.free(resolved_path); + + var close_op = try CloseOperation.start(self.allocator); + var close_op_consumed = false; + defer if (!close_op_consumed) close_op.finish(); + + const flags = if (comptime std.Target.current.isDarwin()) os.O_SYMLINK | os.O_EVTONLY else 0; + const mode = 0; + const fd = try openPosix(self.allocator, resolved_path, flags, mode); + close_op.setHandle(fd); + + var put = try self.allocator.create(OsData.Put); + errdefer self.allocator.destroy(put); + put.* = OsData.Put{ + .value = value, + .putter_frame = undefined, + }; + put.putter_frame = async self.kqPutEvents(close_op, put); + close_op_consumed = true; + errdefer { + put.cancelled = true; + await put.putter_frame; + } + + const result = blk: { + const held = self.os_data.table_lock.acquire(); + defer held.release(); + + const gop = try self.os_data.file_table.getOrPut(resolved_path); + if (gop.found_existing) { + const prev_value = gop.kv.value.value; + await gop.kv.value.putter_frame; + gop.kv.value = put; + break :blk prev_value; + } else { + resolved_path_consumed = true; + gop.kv.value = put; + break :blk null; + } + }; + + return result; + } + + fn kqPutEvents(self: *Self, close_op: *CloseOperation, put: *OsData.Put) void { + global_event_loop.beginOneEvent(); + + defer { + close_op.finish(); + global_event_loop.finishOneEvent(); + } + + while (!put.cancelled) { + if (global_event_loop.bsdWaitKev( + @intCast(usize, close_op.getHandle()), + os.EVFILT_VNODE, + os.NOTE_WRITE | os.NOTE_DELETE, + )) |kev| { + // TODO handle EV_ERROR + if (kev.fflags & os.NOTE_DELETE != 0) { + self.channel.put(Self.Event{ + .id = Event.Id.Delete, + .data = put.value, + }); + } else if (kev.fflags & os.NOTE_WRITE != 0) { + self.channel.put(Self.Event{ + .id = Event.Id.CloseWrite, + .data = put.value, + }); + } + } else |err| switch (err) { + error.EventNotFound => unreachable, + error.ProcessNotFound => unreachable, + error.Overflow => unreachable, + error.AccessDenied, error.SystemResources => |casted_err| { + self.channel.put(casted_err); + }, + } + } + } + + fn addFileLinux(self: *Self, file_path: []const u8, value: V) !?V { + const dirname = std.fs.path.dirname(file_path) orelse "."; + const dirname_with_null = try std.cstr.addNullByte(self.allocator, dirname); + var dirname_with_null_consumed = false; + defer if (!dirname_with_null_consumed) self.channel.free(dirname_with_null); + + const basename = std.fs.path.basename(file_path); + const basename_with_null = try std.cstr.addNullByte(self.allocator, basename); + var basename_with_null_consumed = false; + defer if (!basename_with_null_consumed) self.allocator.free(basename_with_null); + + const wd = try os.inotify_add_watchC( + self.os_data.inotify_fd, + dirname_with_null.ptr, + os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_EXCL_UNLINK, + ); + // wd is either a newly created watch or an existing one. + + const held = self.os_data.table_lock.acquire(); + defer held.release(); + + const gop = try self.os_data.wd_table.getOrPut(wd); + if (!gop.found_existing) { + gop.kv.value = OsData.Dir{ + .dirname = dirname_with_null, + .file_table = OsData.FileTable.init(self.allocator), + }; + dirname_with_null_consumed = true; + } + const dir = &gop.kv.value; + + const file_table_gop = try dir.file_table.getOrPut(basename_with_null); + if (file_table_gop.found_existing) { + const prev_value = file_table_gop.kv.value; + file_table_gop.kv.value = value; + return prev_value; + } else { + file_table_gop.kv.value = value; + basename_with_null_consumed = true; + return null; + } + } + + fn addFileWindows(self: *Self, file_path: []const u8, value: V) !?V { + // TODO we might need to convert dirname and basename to canonical file paths ("short"?) + const dirname = try std.mem.dupe(self.allocator, u8, std.fs.path.dirname(file_path) orelse "."); + var dirname_consumed = false; + defer if (!dirname_consumed) self.allocator.free(dirname); + + const dirname_utf16le = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, dirname); + defer self.allocator.free(dirname_utf16le); + + // TODO https://github.com/ziglang/zig/issues/265 + const basename = std.fs.path.basename(file_path); + const basename_utf16le_null = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, basename); + var basename_utf16le_null_consumed = false; + defer if (!basename_utf16le_null_consumed) self.allocator.free(basename_utf16le_null); + const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1]; + + const dir_handle = try windows.CreateFileW( + dirname_utf16le.ptr, + windows.FILE_LIST_DIRECTORY, + windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE | windows.FILE_SHARE_WRITE, + null, + windows.OPEN_EXISTING, + windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OVERLAPPED, + null, + ); + var dir_handle_consumed = false; + defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle); + + const held = self.os_data.table_lock.acquire(); + defer held.release(); + + const gop = try self.os_data.dir_table.getOrPut(dirname); + if (gop.found_existing) { + const dir = gop.kv.value; + const held_dir_lock = dir.table_lock.acquire(); + defer held_dir_lock.release(); + + const file_gop = try dir.file_table.getOrPut(basename_utf16le_no_null); + if (file_gop.found_existing) { + const prev_value = file_gop.kv.value; + file_gop.kv.value = value; + return prev_value; + } else { + file_gop.kv.value = value; + basename_utf16le_null_consumed = true; + return null; + } + } else { + errdefer _ = self.os_data.dir_table.remove(dirname); + const dir = try self.allocator.create(OsData.Dir); + errdefer self.allocator.destroy(dir); + + dir.* = OsData.Dir{ + .file_table = OsData.FileTable.init(self.allocator), + .table_lock = event.Lock.init(), + .putter_frame = undefined, + }; + gop.kv.value = dir; + assert((try dir.file_table.put(basename_utf16le_no_null, value)) == null); + basename_utf16le_null_consumed = true; + + dir.putter_frame = async self.windowsDirReader(dir_handle, dir); + dir_handle_consumed = true; + + dirname_consumed = true; + + return null; + } + } + + fn windowsDirReader(self: *Self, dir_handle: windows.HANDLE, dir: *OsData.Dir) void { + self.ref(); + defer self.deref(); + + defer os.close(dir_handle); + + var putter_node = std.atomic.Queue(anyframe).Node{ + .data = .{ .putter = @frame() }, + .prev = null, + .next = null, + }; + self.os_data.all_putters.put(&putter_node); + defer _ = self.os_data.all_putters.remove(&putter_node); + + var resume_node = Loop.ResumeNode.Basic{ + .base = Loop.ResumeNode{ + .id = Loop.ResumeNode.Id.Basic, + .handle = @frame(), + .overlapped = windows.OVERLAPPED{ + .Internal = 0, + .InternalHigh = 0, + .Offset = 0, + .OffsetHigh = 0, + .hEvent = null, + }, + }, + }; + var event_buf: [4096]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined; + + // TODO handle this error not in the channel but in the setup + _ = windows.CreateIoCompletionPort( + dir_handle, + global_event_loop.os_data.io_port, + undefined, + undefined, + ) catch |err| { + self.channel.put(err); + return; + }; + + while (!putter_node.data.cancelled) { + { + // TODO only 1 beginOneEvent for the whole function + global_event_loop.beginOneEvent(); + errdefer global_event_loop.finishOneEvent(); + errdefer { + _ = windows.kernel32.CancelIoEx(dir_handle, &resume_node.base.overlapped); + } + suspend { + _ = windows.kernel32.ReadDirectoryChangesW( + dir_handle, + &event_buf, + @intCast(windows.DWORD, event_buf.len), + windows.FALSE, // watch subtree + windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME | + windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE | + windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS | + windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY, + null, // number of bytes transferred (unused for async) + &resume_node.base.overlapped, + null, // completion routine - unused because we use IOCP + ); + } + } + var bytes_transferred: windows.DWORD = undefined; + if (windows.kernel32.GetOverlappedResult(dir_handle, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) { + const err = switch (windows.kernel32.GetLastError()) { + else => |err| windows.unexpectedError(err), + }; + self.channel.put(err); + } else { + // can't use @bytesToSlice because of the special variable length name field + var ptr = event_buf[0..].ptr; + const end_ptr = ptr + bytes_transferred; + var ev: *windows.FILE_NOTIFY_INFORMATION = undefined; + while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += ev.NextEntryOffset) { + ev = @ptrCast(*windows.FILE_NOTIFY_INFORMATION, ptr); + const emit = switch (ev.Action) { + windows.FILE_ACTION_REMOVED => WatchEventId.Delete, + windows.FILE_ACTION_MODIFIED => WatchEventId.CloseWrite, + else => null, + }; + if (emit) |id| { + const basename_utf16le = ([*]u16)(&ev.FileName)[0 .. ev.FileNameLength / 2]; + const user_value = blk: { + const held = dir.table_lock.acquire(); + defer held.release(); + + if (dir.file_table.get(basename_utf16le)) |entry| { + break :blk entry.value; + } else { + break :blk null; + } + }; + if (user_value) |v| { + self.channel.put(Event{ + .id = id, + .data = v, + }); + } + } + if (ev.NextEntryOffset == 0) break; + } + } + } + } + + pub fn removeFile(self: *Self, file_path: []const u8) ?V { + @panic("TODO"); + } + + fn linuxEventPutter(self: *Self) void { + global_event_loop.beginOneEvent(); + + defer { + self.os_data.table_lock.deinit(); + var wd_it = self.os_data.wd_table.iterator(); + while (wd_it.next()) |wd_entry| { + var file_it = wd_entry.value.file_table.iterator(); + while (file_it.next()) |file_entry| { + self.allocator.free(file_entry.key); + } + self.allocator.free(wd_entry.value.dirname); + wd_entry.value.file_table.deinit(); + } + self.os_data.wd_table.deinit(); + global_event_loop.finishOneEvent(); + os.close(self.os_data.inotify_fd); + self.channel.deinit(); + self.allocator.free(self.channel.buffer_nodes); + } + + var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined; + + while (!self.os_data.cancelled) { + const rc = os.linux.read(self.os_data.inotify_fd, &event_buf, event_buf.len); + const errno = os.linux.getErrno(rc); + switch (errno) { + 0 => { + // can't use @bytesToSlice because of the special variable length name field + var ptr = event_buf[0..].ptr; + const end_ptr = ptr + event_buf.len; + var ev: *os.linux.inotify_event = undefined; + while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += @sizeOf(os.linux.inotify_event) + ev.len) { + ev = @ptrCast(*os.linux.inotify_event, ptr); + if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) { + const basename_ptr = ptr + @sizeOf(os.linux.inotify_event); + // `ev.len` counts all bytes in `ev.name` including terminating null byte. + const basename_with_null = basename_ptr[0..ev.len]; + const user_value = blk: { + const held = self.os_data.table_lock.acquire(); + defer held.release(); + + const dir = &self.os_data.wd_table.get(ev.wd).?.value; + if (dir.file_table.get(basename_with_null)) |entry| { + break :blk entry.value; + } else { + break :blk null; + } + }; + if (user_value) |v| { + self.channel.put(Event{ + .id = WatchEventId.CloseWrite, + .data = v, + }); + } + } + } + }, + os.linux.EINTR => continue, + os.linux.EINVAL => unreachable, + os.linux.EFAULT => unreachable, + os.linux.EAGAIN => { + global_event_loop.linuxWaitFd(self.os_data.inotify_fd, os.linux.EPOLLET | os.linux.EPOLLIN); + }, + else => unreachable, + } + } + } + }; +} const test_tmp_dir = "std_event_fs_test"; -// TODO this test is disabled until the async function rewrite is finished. -//test "write a file, watch it, write it again" { -// return error.SkipZigTest; -// const allocator = std.heap.direct_allocator; -// -// // TODO move this into event loop too -// try os.makePath(allocator, test_tmp_dir); -// defer os.deleteTree(test_tmp_dir) catch {}; -// -// var loop: Loop = undefined; -// try loop.initMultiThreaded(allocator); -// defer loop.deinit(); -// -// var result: anyerror!void = error.ResultNeverWritten; -// const handle = try async<allocator> testFsWatchCantFail(&loop, &result); -// defer cancel handle; -// -// loop.run(); -// return result; -//} - -fn testFsWatchCantFail(loop: *Loop, result: *(anyerror!void)) void { - result.* = testFsWatch(loop); +test "write a file, watch it, write it again" { + // TODO provide a way to run tests in evented I/O mode + if (!std.io.is_async) return error.SkipZigTest; + + const allocator = std.heap.direct_allocator; + + // TODO move this into event loop too + try os.makePath(allocator, test_tmp_dir); + defer os.deleteTree(test_tmp_dir) catch {}; + + return testFsWatch(&allocator); } -fn testFsWatch(loop: *Loop) !void { - const file_path = try std.fs.path.join(loop.allocator, [][]const u8{ test_tmp_dir, "file.txt" }); - defer loop.allocator.free(file_path); +fn testFsWatch(allocator: *Allocator) !void { + const file_path = try std.fs.path.join(allocator, [_][]const u8{ test_tmp_dir, "file.txt" }); + defer allocator.free(file_path); const contents = \\line 1 @@ -1350,27 +1326,27 @@ fn testFsWatch(loop: *Loop) !void { const line2_offset = 7; // first just write then read the file - try writeFile(loop, file_path, contents); + try writeFile(allocator, file_path, contents); - const read_contents = try readFile(loop, file_path, 1024 * 1024); + const read_contents = try readFile(allocator, file_path, 1024 * 1024); testing.expectEqualSlices(u8, contents, read_contents); // now watch the file - var watch = try Watch(void).create(loop, 0); - defer watch.destroy(); + var watch = try Watch(void).init(allocator, 0); + defer watch.deinit(); testing.expect((try watch.addFile(file_path, {})) == null); - const ev = async watch.channel.get(); + const ev = watch.channel.get(); var ev_consumed = false; defer if (!ev_consumed) await ev; // overwrite line 2 - const fd = try await openReadWrite(loop, file_path, File.default_mode); + const fd = try await openReadWrite(file_path, File.default_mode); { defer os.close(fd); - try pwritev(loop, fd, []const []const u8{"lorem ipsum"}, line2_offset); + try pwritev(allocator, fd, []const []const u8{"lorem ipsum"}, line2_offset); } ev_consumed = true; @@ -1378,7 +1354,7 @@ fn testFsWatch(loop: *Loop) !void { WatchEventId.CloseWrite => {}, WatchEventId.Delete => @panic("wrong event"), } - const contents_updated = try readFile(loop, file_path, 1024 * 1024); + const contents_updated = try readFile(allocator, file_path, 1024 * 1024); testing.expectEqualSlices(u8, \\line 1 \\lorem ipsum @@ -1390,16 +1366,15 @@ fn testFsWatch(loop: *Loop) !void { pub const OutStream = struct { fd: fd_t, stream: Stream, - loop: *Loop, + allocator: *Allocator, offset: usize, pub const Error = File.WriteError; pub const Stream = event.io.OutStream(Error); - pub fn init(loop: *Loop, fd: fd_t, offset: usize) OutStream { + pub fn init(allocator: *Allocator, fd: fd_t, offset: usize) OutStream { return OutStream{ .fd = fd, - .loop = loop, .offset = offset, .stream = Stream{ .writeFn = writeFn }, }; @@ -1409,23 +1384,22 @@ pub const OutStream = struct { const self = @fieldParentPtr(OutStream, "stream", out_stream); const offset = self.offset; self.offset += bytes.len; - return pwritev(self.loop, self.fd, [][]const u8{bytes}, offset); + return pwritev(self.allocator, self.fd, [_][]const u8{bytes}, offset); } }; pub const InStream = struct { fd: fd_t, stream: Stream, - loop: *Loop, + allocator: *Allocator, offset: usize, pub const Error = PReadVError; // TODO make this not have OutOfMemory pub const Stream = event.io.InStream(Error); - pub fn init(loop: *Loop, fd: fd_t, offset: usize) InStream { + pub fn init(allocator: *Allocator, fd: fd_t, offset: usize) InStream { return InStream{ .fd = fd, - .loop = loop, .offset = offset, .stream = Stream{ .readFn = readFn }, }; @@ -1433,7 +1407,7 @@ pub const InStream = struct { fn readFn(in_stream: *Stream, bytes: []u8) Error!usize { const self = @fieldParentPtr(InStream, "stream", in_stream); - const amt = try preadv(self.loop, self.fd, [][]u8{bytes}, self.offset); + const amt = try preadv(self.allocator, self.fd, [_][]u8{bytes}, self.offset); self.offset += amt; return amt; } diff --git a/lib/std/fifo.zig b/lib/std/fifo.zig index 12d1750df3..e078abcb2b 100644 --- a/lib/std/fifo.zig +++ b/lib/std/fifo.zig @@ -148,7 +148,7 @@ pub fn LinearFifo( var start = self.head + offset; if (start >= self.buf.len) { start -= self.buf.len; - return self.buf[start..self.count - offset]; + return self.buf[start .. self.count - offset]; } else { const end = math.min(self.head + self.count, self.buf.len); return self.buf[start..end]; diff --git a/lib/std/mutex.zig b/lib/std/mutex.zig index 706c699a87..669321c0f5 100644 --- a/lib/std/mutex.zig +++ b/lib/std/mutex.zig @@ -74,7 +74,7 @@ else pub fn release(self: Held) void { switch (@atomicRmw(State, &self.mutex.state, .Xchg, .Unlocked, .Release)) { .Locked => {}, - .Sleeping => self.mutex.parker.unpark(@ptrCast(*const u32, &self.mutex.state)), + .Sleeping => self.mutex.parker.unpark(@ptrCast(*const u32, &self.mutex.state)), .Unlocked => unreachable, // unlocking an unlocked mutex else => unreachable, // should never be anything else } @@ -112,7 +112,7 @@ else if (@atomicRmw(State, &self.state, .Xchg, .Sleeping, .Acquire) == .Unlocked) return Held{ .mutex = self }; state = .Sleeping; - self.parker.park(@ptrCast(*const u32, &self.state), @enumToInt(State.Sleeping)); + self.parker.park(@ptrCast(*const u32, &self.state), @enumToInt(State.Sleeping)); } } }; diff --git a/lib/std/net.zig b/lib/std/net.zig index 9a602c0105..7a7b2de026 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -1362,7 +1362,7 @@ pub const StreamServer = struct { pub const Connection = struct { file: fs.File, - address: Address + address: Address, }; /// If this function succeeds, the returned `Connection` is a caller-managed resource. diff --git a/lib/std/os.zig b/lib/std/os.zig index cda65b7ea7..7fcdb7f3ff 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -2753,8 +2753,8 @@ pub fn sched_getaffinity(pid: pid_t) SchedGetAffinityError!cpu_set_t { /// Used to convert a slice to a null terminated slice on the stack. /// TODO https://github.com/ziglang/zig/issues/287 -pub fn toPosixPath(file_path: []const u8) ![PATH_MAX-1:0]u8 { - var path_with_null: [PATH_MAX-1:0]u8 = undefined; +pub fn toPosixPath(file_path: []const u8) ![PATH_MAX - 1:0]u8 { + var path_with_null: [PATH_MAX - 1:0]u8 = undefined; // >= rather than > to make room for the null byte if (file_path.len >= PATH_MAX) return error.NameTooLong; mem.copy(u8, &path_with_null, file_path); diff --git a/lib/std/os/bits/linux.zig b/lib/std/os/bits/linux.zig index 3cafb8ba0b..118223d03c 100644 --- a/lib/std/os/bits/linux.zig +++ b/lib/std/os/bits/linux.zig @@ -1124,7 +1124,6 @@ pub const io_uring_params = extern struct { pub const IORING_FEAT_SINGLE_MMAP = 1 << 0; - // io_uring_params.flags /// io_context is polled diff --git a/lib/std/os/uefi/protocols/hii.zig b/lib/std/os/uefi/protocols/hii.zig index 5dd9095d14..55326b25b3 100644 --- a/lib/std/os/uefi/protocols/hii.zig +++ b/lib/std/os/uefi/protocols/hii.zig @@ -26,6 +26,7 @@ pub const HIIPackageHeader = packed struct { /// The header found at the start of each package list. pub const HIIPackageList = extern struct { package_list_guid: Guid, + /// The size of the package list (in bytes), including the header. package_list_length: u32, diff --git a/lib/std/os/uefi/status.zig b/lib/std/os/uefi/status.zig index 7c7f98b450..171f3cb05c 100644 --- a/lib/std/os/uefi/status.zig +++ b/lib/std/os/uefi/status.zig @@ -5,82 +5,120 @@ pub const success: usize = 0; /// The image failed to load. pub const load_error: usize = high_bit | 1; + /// A parameter was incorrect. pub const invalid_parameter: usize = high_bit | 2; + /// The operation is not supported. pub const unsupported: usize = high_bit | 3; + /// The buffer was not the proper size for the request. pub const bad_buffer_size: usize = high_bit | 4; + /// The buffer is not large enough to hold the requested data. The required buffer size is returned in the appropriate parameter when this error occurs. pub const buffer_too_small: usize = high_bit | 5; + /// There is no data pending upon return. pub const not_ready: usize = high_bit | 6; + /// The physical device reported an error while attempting the operation. pub const device_error: usize = high_bit | 7; + /// The device cannot be written to. pub const write_protected: usize = high_bit | 8; + /// A resource has run out. pub const out_of_resources: usize = high_bit | 9; + /// An inconstancy was detected on the file system causing the operating to fail. pub const volume_corrupted: usize = high_bit | 10; + /// There is no more space on the file system. pub const volume_full: usize = high_bit | 11; + /// The device does not contain any medium to perform the operation. pub const no_media: usize = high_bit | 12; + /// The medium in the device has changed since the last access. pub const media_changed: usize = high_bit | 13; + /// The item was not found. pub const not_found: usize = high_bit | 14; + /// Access was denied. pub const access_denied: usize = high_bit | 15; + /// The server was not found or did not respond to the request. pub const no_response: usize = high_bit | 16; + /// A mapping to a device does not exist. pub const no_mapping: usize = high_bit | 17; + /// The timeout time expired. pub const timeout: usize = high_bit | 18; + /// The protocol has not been started. pub const not_started: usize = high_bit | 19; + /// The protocol has already been started. pub const already_started: usize = high_bit | 20; + /// The operation was aborted. pub const aborted: usize = high_bit | 21; + /// An ICMP error occurred during the network operation. pub const icmp_error: usize = high_bit | 22; + /// A TFTP error occurred during the network operation. pub const tftp_error: usize = high_bit | 23; + /// A protocol error occurred during the network operation. pub const protocol_error: usize = high_bit | 24; + /// The function encountered an internal version that was incompatible with a version requested by the caller. pub const incompatible_version: usize = high_bit | 25; + /// The function was not performed due to a security violation. pub const security_violation: usize = high_bit | 26; + /// A CRC error was detected. pub const crc_error: usize = high_bit | 27; + /// Beginning or end of media was reached pub const end_of_media: usize = high_bit | 28; + /// The end of the file was reached. pub const end_of_file: usize = high_bit | 31; + /// The language specified was invalid. pub const invalid_language: usize = high_bit | 32; + /// The security status of the data is unknown or compromised and the data must be updated or replaced to restore a valid security status. pub const compromised_data: usize = high_bit | 33; + /// There is an address conflict address allocation pub const ip_address_conflict: usize = high_bit | 34; + /// A HTTP error occurred during the network operation. pub const http_error: usize = high_bit | 35; /// The string contained one or more characters that the device could not render and were skipped. pub const warn_unknown_glyph: usize = 1; + /// The handle was closed, but the file was not deleted. pub const warn_delete_failure: usize = 2; + /// The handle was closed, but the data to the file was not flushed properly. pub const warn_write_failure: usize = 3; + /// The resulting buffer was too small, and the data was truncated to the buffer size. pub const warn_buffer_too_small: usize = 4; + /// The data has not been updated within the timeframe set by localpolicy for this type of data. pub const warn_stale_data: usize = 5; + /// The resulting buffer contains UEFI-compliant file system. pub const warn_file_system: usize = 6; + /// The operation will be processed across a system reset. pub const warn_reset_required: usize = 7; diff --git a/lib/std/os/uefi/tables/table_header.zig b/lib/std/os/uefi/tables/table_header.zig index 527c7a61b9..d5d4094232 100644 --- a/lib/std/os/uefi/tables/table_header.zig +++ b/lib/std/os/uefi/tables/table_header.zig @@ -1,6 +1,7 @@ pub const TableHeader = extern struct { signature: u64, revision: u32, + /// The size, in bytes, of the entire table including the TableHeader header_size: u32, crc32: u32, diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 4bf86bc4fd..5fc18accb8 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -192,7 +192,7 @@ pub const FindFirstFileError = error{ }; pub fn FindFirstFile(dir_path: []const u8, find_file_data: *WIN32_FIND_DATAW) FindFirstFileError!HANDLE { - const dir_path_w = try sliceToPrefixedSuffixedFileW(dir_path, [_]u16{ '\\', '*'}); + const dir_path_w = try sliceToPrefixedSuffixedFileW(dir_path, [_]u16{ '\\', '*' }); const handle = kernel32.FindFirstFileW(&dir_path_w, find_file_data); if (handle == INVALID_HANDLE_VALUE) { @@ -932,7 +932,7 @@ pub fn wToPrefixedFileW(s: []const u16) ![PATH_MAX_WIDE:0]u16 { // TODO https://github.com/ziglang/zig/issues/2765 var result: [PATH_MAX_WIDE:0]u16 = undefined; - const start_index = if (mem.startsWith(u16, s, [_]u16{'\\', '?'})) 0 else blk: { + const start_index = if (mem.startsWith(u16, s, [_]u16{ '\\', '?' })) 0 else blk: { const prefix = [_]u16{ '\\', '?', '?', '\\' }; mem.copy(u16, result[0..], prefix); break :blk prefix.len; @@ -942,7 +942,6 @@ pub fn wToPrefixedFileW(s: []const u16) ![PATH_MAX_WIDE:0]u16 { mem.copy(u16, result[start_index..], s); result[end_index] = 0; return result; - } pub fn sliceToPrefixedSuffixedFileW(s: []const u8, comptime suffix: []const u16) ![PATH_MAX_WIDE + suffix.len:0]u16 { diff --git a/lib/std/os/windows/ws2_32.zig b/lib/std/os/windows/ws2_32.zig index c34077a9dc..0554f09705 100644 --- a/lib/std/os/windows/ws2_32.zig +++ b/lib/std/os/windows/ws2_32.zig @@ -106,12 +106,7 @@ pub const WSAOVERLAPPED = extern struct { hEvent: ?WSAEVENT, }; -pub const WSAOVERLAPPED_COMPLETION_ROUTINE = extern fn ( - dwError: DWORD, - cbTransferred: DWORD, - lpOverlapped: *WSAOVERLAPPED, - dwFlags: DWORD -) void; +pub const WSAOVERLAPPED_COMPLETION_ROUTINE = extern fn (dwError: DWORD, cbTransferred: DWORD, lpOverlapped: *WSAOVERLAPPED, dwFlags: DWORD) void; pub const WSA_INVALID_HANDLE = 6; pub const WSA_NOT_ENOUGH_MEMORY = 8; @@ -209,11 +204,12 @@ pub const WSA_QOS_ESDMODEOBJ = 11029; pub const WSA_QOS_ESHAPERATEOBJ = 11030; pub const WSA_QOS_RESERVED_PETYPE = 11031; - /// no parameters const IOC_VOID = 0x80000000; + /// copy out parameters const IOC_OUT = 0x40000000; + /// copy in parameters const IOC_IN = 0x80000000; diff --git a/lib/std/zig/ast.zig b/lib/std/zig/ast.zig index 9f9064f88d..63c6168ed9 100644 --- a/lib/std/zig/ast.zig +++ b/lib/std/zig/ast.zig @@ -2204,7 +2204,7 @@ pub const Node = struct { }; pub const VarType = struct { - base: Node, + base: Node = Node{ .id = .VarType }, token: TokenIndex, pub fn iterate(self: *VarType, index: usize) ?*Node { diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index fd15cda11a..3ece8d150b 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -410,10 +410,16 @@ fn parseContainerField(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*No var align_expr: ?*Node = null; var type_expr: ?*Node = null; if (eatToken(it, .Colon)) |_| { - type_expr = try expectNode(arena, it, tree, parseTypeExpr, AstError{ - .ExpectedTypeExpr = AstError.ExpectedTypeExpr{ .token = it.index }, - }); - align_expr = try parseByteAlign(arena, it, tree); + if (eatToken(it, .Keyword_var)) |var_tok| { + const node = try arena.create(ast.Node.VarType); + node.* = .{ .token = var_tok }; + type_expr = &node.base; + } else { + type_expr = try expectNode(arena, it, tree, parseTypeExpr, AstError{ + .ExpectedTypeExpr = AstError.ExpectedTypeExpr{ .token = it.index }, + }); + align_expr = try parseByteAlign(arena, it, tree); + } } const value_expr = if (eatToken(it, .Equal)) |_| @@ -576,7 +582,8 @@ fn parseIfStatement(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node /// LabeledStatement <- BlockLabel? (Block / LoopStatement) fn parseLabeledStatement(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node { - const label_token = parseBlockLabel(arena, it, tree); + var colon: TokenIndex = undefined; + const label_token = parseBlockLabel(arena, it, tree, &colon); if (try parseBlock(arena, it, tree)) |node| { node.cast(Node.Block).?.label = label_token; @@ -757,7 +764,8 @@ fn parseBlockExprStatement(arena: *Allocator, it: *TokenIterator, tree: *Tree) ! /// BlockExpr <- BlockLabel? Block fn parseBlockExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) Error!?*Node { - const label_token = parseBlockLabel(arena, it, tree); + var colon: TokenIndex = undefined; + const label_token = parseBlockLabel(arena, it, tree, &colon); const block_node = (try parseBlock(arena, it, tree)) orelse { if (label_token) |label| { putBackToken(it, label + 1); // ":" @@ -913,7 +921,8 @@ fn parsePrimaryExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node return &node.base; } - const label = parseBlockLabel(arena, it, tree); + var colon: TokenIndex = undefined; + const label = parseBlockLabel(arena, it, tree, &colon); if (try parseLoopExpr(arena, it, tree)) |node| { if (node.cast(Node.For)) |for_node| { for_node.label = label; @@ -1354,7 +1363,8 @@ fn parseIfTypeExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node { /// <- BlockLabel Block /// / BlockLabel? LoopTypeExpr fn parseLabeledTypeExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node { - const label = parseBlockLabel(arena, it, tree); + var colon: TokenIndex = undefined; + const label = parseBlockLabel(arena, it, tree, &colon); if (label) |token| { if (try parseBlock(arena, it, tree)) |node| { @@ -1372,12 +1382,9 @@ fn parseLabeledTypeExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*N return node; } - if (label != null) { - // If we saw a label, there should have been a block next - try tree.errors.push(AstError{ - .ExpectedLBrace = AstError.ExpectedLBrace{ .token = it.index }, - }); - return error.ParseError; + if (label) |token| { + putBackToken(it, colon); + putBackToken(it, token); } return null; } @@ -1641,9 +1648,12 @@ fn parseBreakLabel(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node { } /// BlockLabel <- IDENTIFIER COLON -fn parseBlockLabel(arena: *Allocator, it: *TokenIterator, tree: *Tree) ?TokenIndex { +fn parseBlockLabel(arena: *Allocator, it: *TokenIterator, tree: *Tree, colon_token: *TokenIndex) ?TokenIndex { const identifier = eatToken(it, .Identifier) orelse return null; - if (eatToken(it, .Colon) != null) return identifier; + if (eatToken(it, .Colon)) |colon| { + colon_token.* = colon; + return identifier; + } putBackToken(it, identifier); return null; } diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index ce19588722..0d38ec5ccb 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -1,3 +1,30 @@ +test "zig fmt: var struct field" { + try testCanonical( + \\pub const Pointer = struct { + \\ sentinel: var, + \\}; + \\ + ); +} + +test "zig fmt: sentinel-terminated array type" { + try testCanonical( + \\pub fn cStrToPrefixedFileW(s: [*:0]const u8) ![PATH_MAX_WIDE:0]u16 { + \\ return sliceToPrefixedFileW(mem.toSliceConst(u8, s)); + \\} + \\ + ); +} + +test "zig fmt: sentinel-terminated slice type" { + try testCanonical( + \\pub fn toSlice(self: Buffer) [:0]u8 { + \\ return self.list.toSlice()[0..self.len()]; + \\} + \\ + ); +} + test "zig fmt: anon literal in array" { try testCanonical( \\var arr: [2]Foo = .{ diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index d6e2456455..1b6aaf7102 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -477,7 +477,14 @@ fn renderExpression( ast.Node.PrefixOp.Op.SliceType => |ptr_info| { try renderToken(tree, stream, prefix_op_node.op_token, indent, start_col, Space.None); // [ - try renderToken(tree, stream, tree.nextToken(prefix_op_node.op_token), indent, start_col, Space.None); // ] + if (ptr_info.sentinel) |sentinel| { + const colon_token = tree.prevToken(sentinel.firstToken()); + try renderToken(tree, stream, colon_token, indent, start_col, Space.None); // : + try renderExpression(allocator, stream, tree, indent, start_col, sentinel, Space.None); + try renderToken(tree, stream, tree.nextToken(sentinel.lastToken()), indent, start_col, Space.None); // ] + } else { + try renderToken(tree, stream, tree.nextToken(prefix_op_node.op_token), indent, start_col, Space.None); // ] + } if (ptr_info.allowzero_token) |allowzero_token| { try renderToken(tree, stream, allowzero_token, indent, start_col, Space.Space); // allowzero |
