authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-12-30 12:59:32+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-01-03 15:45:10+00:00
loge57c557ad41188f85984cb8e6b79c8432cb95ec2
treeb14249ce9c6622855640aea8d4c7f2ff2437b1ae
parenta1d4120fd9a3b34bde79d34dd14725d751021fc6
signaturelock-open Commit is signed but in an unrecognized format.

std.Io.Threaded: hugely improve Windows and NetBSD support

The most interesting thing here is the replacement of the pthread futex implementation with an implementation based on thread park/unpark APIs. Thread parking tends to be the primitive provided by systems which do not have a futex primitive, such as NetBSD, so this implementation is far more efficient than the pthread one. It is also useful on Windows, where `RtlWaitOnAddress` is itself a userland implementation based on thread park/unpark; we can implement it ourselves including support for features which Windows' implementation lacks, such as cancelation and waking a number of waiters with 1<n<infinity. Compared to the pthread implementation, this thread-parking-based one also supports full robust cancelation. Thread parking also turns out to be useful for implementing `sleep`, so is now used for that on Windows and NetBSD. This commit also introduces proper cancelation support for most Windows operations. The most notable omission right now is DNS lookups through `GetAddrInfoEx`, just because they're a little more work due to having a unique cancelation mechanism---but the machinery is all there, so I'll finish gluing it together soon. As of this commit, there are very few parts of `Io.Threaded` which do not support full robust cancelation. The only ones which actually really matter (because they could block for a prolonged period of time) are DNS lookups on Windows (as discussed above) and futex waits on WASM.

8 files changed, 1681 insertions(+), 1270 deletions(-)

lib/std/Io/Threaded.zig+1622-1199
...@@ -105,12 +105,7 @@ pub const Environ = struct {...@@ -105,12 +105,7 @@ pub const Environ = struct {
105 };105 };
106};106};
107107
108pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum {108pub const RobustCancel = enum { enabled, disabled };
109 enabled,
110 disabled,
111} else enum {
112 disabled,
113};
114109
115pub const Pid = if (native_os == .linux) enum(posix.pid_t) {110pub const Pid = if (native_os == .linux) enum(posix.pid_t) {
116 unknown = 0,111 unknown = 0,
...@@ -514,13 +509,21 @@ const AwaitableId = enum(@Int(.unsigned, @bitSizeOf(usize) - 3)) {...@@ -514,13 +509,21 @@ const AwaitableId = enum(@Int(.unsigned, @bitSizeOf(usize) - 3)) {
514509
515const Thread = struct {510const Thread = struct {
516 next: ?*Thread,511 next: ?*Thread,
517 /// The value that needs to be passed to pthread_kill or tgkill in order to512
518 /// send a signal.513 id: std.Thread.Id,
519 signalee_id: SignaleeId,514 handle: Handle,
520515
521 status: std.atomic.Value(Status),516 status: std.atomic.Value(Status),
522517
523 cancel_protection: Io.CancelProtection,518 cancel_protection: Io.CancelProtection,
519 /// Always released when `Status.cancelation` is set to `.parked`.
520 futex_waiter: if (use_parking_futex) ?*parking_futex.Waiter else ?noreturn,
521
522 const Handle = Handle: {
523 if (std.Thread.use_pthreads) break :Handle std.c.pthread_t;
524 if (builtin.target.os.tag == .windows) break :Handle windows.HANDLE;
525 break :Handle void;
526 };
524527
525 const Status = packed struct(usize) {528 const Status = packed struct(usize) {
526 /// The specific values of these enum fields are chosen to simplify the implementation of529 /// The specific values of these enum fields are chosen to simplify the implementation of
...@@ -531,7 +534,7 @@ const Thread = struct {...@@ -531,7 +534,7 @@ const Thread = struct {
531 none = 0b000,534 none = 0b000,
532535
533 /// The thread is parked in a cancelable futex wait or sleep.536 /// The thread is parked in a cancelable futex wait or sleep.
534 /// Only applicable on Windows, NetBSD, and Illumos.537 /// Only applicable if `use_parking_futex` or `use_parking_sleep`.
535 /// To request cancelation, set the status to `.canceling` and unpark the thread.538 /// To request cancelation, set the status to `.canceling` and unpark the thread.
536 /// To unpark for another reason (futex wake), set the status to `.none` and unpark the thread.539 /// To unpark for another reason (futex wake), set the status to `.none` and unpark the thread.
537 parked = 0b001,540 parked = 0b001,
...@@ -540,8 +543,8 @@ const Thread = struct {...@@ -540,8 +543,8 @@ const Thread = struct {
540 /// To request cancelation, set the status to `.blocked_canceling` and repeatedly interrupt the system call until the status changes.543 /// To request cancelation, set the status to `.blocked_canceling` and repeatedly interrupt the system call until the status changes.
541 blocked = 0b011,544 blocked = 0b011,
542545
543 /// Windows-only: the thread is blocked on a DNS query.546 /// Windows-only: the thread is blocked in a call to `GetAddrInfoExW`.
544 /// To request cancelation, set the status to `.canceling` and call `DnsCancelQuery`.547 /// To request cancelation, set the status to `.canceling` and call `GetAddrInfoExCancel`.
545 blocked_windows_dns = 0b010,548 blocked_windows_dns = 0b010,
546549
547 /// The thread has an outstanding cancelation request but is not in a cancelable operation.550 /// The thread has an outstanding cancelation request but is not in a cancelable operation.
...@@ -597,10 +600,6 @@ const Thread = struct {...@@ -597,10 +600,6 @@ const Thread = struct {
597 }600 }
598 }601 }
599602
600 fn currentSignaleeId() SignaleeId {
601 return if (std.Thread.use_pthreads) std.c.pthread_self() else std.Thread.getCurrentId();
602 }
603
604 fn futexWaitUncancelable(ptr: *const u32, expect: u32, timeout_ns: ?u64) void {603 fn futexWaitUncancelable(ptr: *const u32, expect: u32, timeout_ns: ?u64) void {
605 return Thread.futexWaitInner(ptr, expect, true, timeout_ns) catch unreachable;604 return Thread.futexWaitInner(ptr, expect, true, timeout_ns) catch unreachable;
606 }605 }
...@@ -614,8 +613,19 @@ const Thread = struct {...@@ -614,8 +613,19 @@ const Thread = struct {
614613
615 if (builtin.single_threaded) unreachable; // nobody would ever wake us614 if (builtin.single_threaded) unreachable; // nobody would ever wake us
616615
617 if (builtin.cpu.arch.isWasm()) {616 if (use_parking_futex) {
617 return parking_futex.wait(
618 ptr,
619 expect,
620 uncancelable,
621 if (timeout_ns) |ns| .{ .duration = .{
622 .raw = .fromNanoseconds(ns),
623 .clock = .boot,
624 } } else .none,
625 );
626 } else if (builtin.cpu.arch.isWasm()) {
618 comptime assert(builtin.cpu.has(.wasm, .atomics));627 comptime assert(builtin.cpu.has(.wasm, .atomics));
628 // TODO implement cancelation for WASM futex waits by signaling the futex
619 if (!uncancelable) try Thread.checkCancel();629 if (!uncancelable) try Thread.checkCancel();
620 const to: i64 = if (timeout_ns) |ns| ns else -1;630 const to: i64 = if (timeout_ns) |ns| ns else -1;
621 const signed_expect: i32 = @bitCast(expect);631 const signed_expect: i32 = @bitCast(expect);
...@@ -689,24 +699,6 @@ const Thread = struct {...@@ -689,24 +699,6 @@ const Thread = struct {
689 else => recoverableOsBugDetected(),699 else => recoverableOsBugDetected(),
690 }700 }
691 },701 },
692 .windows => {
693 var timeout_value: windows.LARGE_INTEGER = undefined;
694 var timeout_ptr: ?*const windows.LARGE_INTEGER = null;
695 // NTDLL functions work with time in units of 100 nanoseconds.
696 // Positive values are absolute deadlines while negative values are relative durations.
697 if (timeout_ns) |delay| {
698 timeout_value = @as(windows.LARGE_INTEGER, @intCast(delay / 100));
699 timeout_value = -timeout_value;
700 timeout_ptr = &timeout_value;
701 }
702 if (!uncancelable) try Thread.checkCancel();
703 switch (windows.ntdll.RtlWaitOnAddress(ptr, &expect, @sizeOf(@TypeOf(expect)), timeout_ptr)) {
704 .SUCCESS => {},
705 .CANCELLED => {},
706 .TIMEOUT => {}, // timeout
707 else => recoverableOsBugDetected(),
708 }
709 },
710 .freebsd => {702 .freebsd => {
711 const flags = @intFromEnum(std.c.UMTX_OP.WAIT_UINT_PRIVATE);703 const flags = @intFromEnum(std.c.UMTX_OP.WAIT_UINT_PRIVATE);
712 var tm_size: usize = 0;704 var tm_size: usize = 0;
...@@ -738,7 +730,7 @@ const Thread = struct {...@@ -738,7 +730,7 @@ const Thread = struct {
738 tm_ptr = &tm;730 tm_ptr = &tm;
739 tm = timestampToPosix(ns);731 tm = timestampToPosix(ns);
740 }732 }
741 if (thread) |t| try t.beginSyscall();733 const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start();
742 const rc = std.c.futex(734 const rc = std.c.futex(
743 ptr,735 ptr,
744 std.c.FUTEX.WAIT | std.c.FUTEX.PRIVATE_FLAG,736 std.c.FUTEX.WAIT | std.c.FUTEX.PRIVATE_FLAG,
...@@ -746,7 +738,7 @@ const Thread = struct {...@@ -746,7 +738,7 @@ const Thread = struct {
746 tm_ptr,738 tm_ptr,
747 null, // uaddr2 is ignored739 null, // uaddr2 is ignored
748 );740 );
749 if (thread) |t| t.endSyscall();741 syscall.finish();
750 if (is_debug) switch (posix.errno(rc)) {742 if (is_debug) switch (posix.errno(rc)) {
751 .SUCCESS => {},743 .SUCCESS => {},
752 .NOSYS => unreachable, // constant op known good value744 .NOSYS => unreachable, // constant op known good value
...@@ -765,9 +757,9 @@ const Thread = struct {...@@ -765,9 +757,9 @@ const Thread = struct {
765 } else {757 } else {
766 timeout_us = 0;758 timeout_us = 0;
767 }759 }
768 if (thread) |t| try t.beginSyscall();760 const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start();
769 const rc = std.c.umtx_sleep(@ptrCast(ptr), @bitCast(expect), timeout_us);761 const rc = std.c.umtx_sleep(@ptrCast(ptr), @bitCast(expect), timeout_us);
770 if (thread) |t| t.endSyscall();762 syscall.finish();
771 if (is_debug) switch (std.posix.errno(rc)) {763 if (is_debug) switch (std.posix.errno(rc)) {
772 .SUCCESS => {},764 .SUCCESS => {},
773 .BUSY => {}, // ptr != expect765 .BUSY => {}, // ptr != expect
...@@ -777,14 +769,7 @@ const Thread = struct {...@@ -777,14 +769,7 @@ const Thread = struct {
777 else => unreachable,769 else => unreachable,
778 };770 };
779 },771 },
780 else => if (std.Thread.use_pthreads) {772 else => @compileError("unimplemented: futexWait"),
781 // TODO integrate the following function being called with robust cancelation.
782 return pthreads_futex.wait(ptr, expect, timeout_ns) catch |err| switch (err) {
783 error.Timeout => {},
784 };
785 } else {
786 @compileError("unimplemented: futexWait");
787 },
788 }773 }
789 }774 }
790775
...@@ -794,7 +779,9 @@ const Thread = struct {...@@ -794,7 +779,9 @@ const Thread = struct {
794779
795 if (builtin.single_threaded) return; // nothing to wake up780 if (builtin.single_threaded) return; // nothing to wake up
796781
797 if (builtin.cpu.arch.isWasm()) {782 if (use_parking_futex) {
783 return parking_futex.wake(ptr, max_waiters);
784 } else if (builtin.cpu.arch.isWasm()) {
798 comptime assert(builtin.cpu.has(.wasm, .atomics));785 comptime assert(builtin.cpu.has(.wasm, .atomics));
799 const woken_count = asm volatile (786 const woken_count = asm volatile (
800 \\local.get %[ptr]787 \\local.get %[ptr]
...@@ -839,12 +826,6 @@ const Thread = struct {...@@ -839,12 +826,6 @@ const Thread = struct {
839 }826 }
840 }827 }
841 },828 },
842 .windows => {
843 switch (max_waiters) {
844 1 => windows.ntdll.RtlWakeAddressSingle(ptr),
845 else => windows.ntdll.RtlWakeAddressAll(ptr),
846 }
847 },
848 .freebsd => {829 .freebsd => {
849 const rc = std.c._umtx_op(830 const rc = std.c._umtx_op(
850 @intFromPtr(ptr),831 @intFromPtr(ptr),
...@@ -877,11 +858,7 @@ const Thread = struct {...@@ -877,11 +858,7 @@ const Thread = struct {
877 @min(max_waiters, std.math.maxInt(c_int)),858 @min(max_waiters, std.math.maxInt(c_int)),
878 );859 );
879 },860 },
880 else => if (std.Thread.use_pthreads) {861 else => @compileError("unimplemented: futexWake"),
881 return pthreads_futex.wake(ptr, max_waiters);
882 } else {
883 @compileError("unimplemented: futexWake");
884 },
885 }862 }
886 }863 }
887864
...@@ -905,10 +882,14 @@ const Thread = struct {...@@ -905,10 +882,14 @@ const Thread = struct {
905 .parked => thread.status.cmpxchgWeak(882 .parked => thread.status.cmpxchgWeak(
906 .{ .cancelation = .parked, .awaitable = awaitable },883 .{ .cancelation = .parked, .awaitable = awaitable },
907 .{ .cancelation = .canceling, .awaitable = awaitable },884 .{ .cancelation = .canceling, .awaitable = awaitable },
908 .monotonic,885 .acquire, // acquire `thread.futex_waiter`
909 .monotonic,886 .monotonic,
910 ) orelse {887 ) orelse {
911 if (true) @panic("MLUGG TODO: unpark thread");888 if (!use_parking_futex and !use_parking_sleep) unreachable;
889 if (thread.futex_waiter) |futex_waiter| {
890 parking_futex.removeCanceledWaiter(futex_waiter);
891 }
892 unpark(&.{thread.id}, null);
912 return false;893 return false;
913 },894 },
914895
...@@ -924,7 +905,15 @@ const Thread = struct {...@@ -924,7 +905,15 @@ const Thread = struct {
924 .{ .cancelation = .canceling, .awaitable = awaitable },905 .{ .cancelation = .canceling, .awaitable = awaitable },
925 .monotonic,906 .monotonic,
926 .monotonic,907 .monotonic,
927 ) orelse return false,908 ) orelse {
909 if (builtin.target.os.tag != .windows) unreachable;
910 if (true) {
911 // TODO: cancel Windows DNS queries. This code path is currently impossible
912 // as `netLookupFallible` doesn't actually use `.blocked_windows_dns` yet.
913 unreachable;
914 }
915 return false;
916 },
928917
929 .canceling, .canceled => {918 .canceling, .canceled => {
930 // This can happen when the task start raced with the cancelation, so the thread919 // This can happen when the task start raced with the cancelation, so the thread
...@@ -951,26 +940,38 @@ const Thread = struct {...@@ -951,26 +940,38 @@ const Thread = struct {
951 const bad_status: Status = .{ .cancelation = .blocked_canceling, .awaitable = awaitable };940 const bad_status: Status = .{ .cancelation = .blocked_canceling, .awaitable = awaitable };
952 if (thread.status.load(.monotonic) != bad_status) return false;941 if (thread.status.load(.monotonic) != bad_status) return false;
953942
954 // The thread ID can be read non-atomically because it never changes and was released by the943 // The thread ID and/or handle can be read non-atomically because they never change and were
955 // store that made `thread` available to us.944 // released by the store that made `thread` available to us.
956 const signalee_id = thread.signalee_id;
957945
958 if (std.Thread.use_pthreads) {946 if (std.Thread.use_pthreads) {
959 if (std.c.pthread_kill(signalee_id, .IO) != 0) return false;947 return switch (std.c.pthread_kill(thread.handle, .IO)) {
960 } else if (native_os == .linux) {948 0 => true,
961 const pid: posix.pid_t = pid: {949 else => false,
962 const cached_pid = @atomicLoad(Pid, &t.pid, .monotonic);
963 if (cached_pid != .unknown) break :pid @intFromEnum(cached_pid);
964 const pid = std.os.linux.getpid();
965 @atomicStore(Pid, &t.pid, @enumFromInt(pid), .monotonic);
966 break :pid pid;
967 };950 };
968 if (std.os.linux.tgkill(pid, @bitCast(signalee_id), .IO) != 0) return false;951 } else switch (builtin.target.os.tag) {
969 } else {952 .linux => {
970 @compileError("MLUGG TODO");953 const pid: posix.pid_t = pid: {
954 const cached_pid = @atomicLoad(Pid, &t.pid, .monotonic);
955 if (cached_pid != .unknown) break :pid @intFromEnum(cached_pid);
956 const pid = std.os.linux.getpid();
957 @atomicStore(Pid, &t.pid, @enumFromInt(pid), .monotonic);
958 break :pid pid;
959 };
960 return switch (std.os.linux.tgkill(pid, @bitCast(thread.id), .IO)) {
961 0 => true,
962 else => false,
963 };
964 },
965 .windows => {
966 var iosb: windows.IO_STATUS_BLOCK = undefined;
967 return switch (windows.ntdll.NtCancelSynchronousIoFile(thread.handle, null, &iosb)) {
968 .NOT_FOUND => true, // this might mean the operation hasn't started yet
969 .SUCCESS => false, // the OS confirmed that our cancelation worked
970 else => false,
971 };
972 },
973 else => return false,
971 }974 }
972
973 return true;
974 }975 }
975976
976 /// Like a `*Thread`, but 2 bits smaller than a pointer (because the LSBs are always 0 due to977 /// Like a `*Thread`, but 2 bits smaller than a pointer (because the LSBs are always 0 due to
...@@ -1069,6 +1070,18 @@ const Syscall = struct {...@@ -1069,6 +1070,18 @@ const Syscall = struct {
1069 s.finish();1070 s.finish();
1070 return posix.unexpectedErrno(err);1071 return posix.unexpectedErrno(err);
1071 }1072 }
1073 /// Convenience wrapper which calls `finish`, then calls `windows.statusBug`.
1074 fn ntstatusBug(s: Syscall, status: windows.NTSTATUS) Io.UnexpectedError {
1075 @branchHint(.cold);
1076 s.finish();
1077 return windows.statusBug(status);
1078 }
1079 /// Convenience wrapper which calls `finish`, then calls `windows.unexpectedStatus`.
1080 fn unexpectedNtstatus(s: Syscall, status: windows.NTSTATUS) Io.UnexpectedError {
1081 @branchHint(.cold);
1082 s.finish();
1083 return windows.unexpectedStatus(status);
1084 }
1072};1085};
10731086
1074const max_iovecs_len = 8;1087const max_iovecs_len = 8;
...@@ -1233,15 +1246,45 @@ fn join(t: *Threaded) void {...@@ -1233,15 +1246,45 @@ fn join(t: *Threaded) void {
1233fn worker(t: *Threaded) void {1246fn worker(t: *Threaded) void {
1234 var thread: Thread = .{1247 var thread: Thread = .{
1235 .next = undefined,1248 .next = undefined,
1236 .signalee_id = Thread.currentSignaleeId(),1249 .id = std.Thread.getCurrentId(),
1250 .handle = handle: {
1251 if (std.Thread.use_pthreads) break :handle std.c.pthread_self();
1252 if (builtin.target.os.tag == .windows) break :handle undefined; // populated below
1253 },
1237 .status = .init(.{1254 .status = .init(.{
1238 .cancelation = .none,1255 .cancelation = .none,
1239 .awaitable = .null,1256 .awaitable = .null,
1240 }),1257 }),
1241 .cancel_protection = .unblocked,1258 .cancel_protection = .unblocked,
1259 .futex_waiter = undefined,
1242 };1260 };
1243 Thread.current = &thread;1261 Thread.current = &thread;
12441262
1263 if (builtin.target.os.tag == .windows) {
1264 assert(windows.ntdll.NtOpenThread(
1265 &thread.handle,
1266 .{
1267 .SPECIFIC = .{
1268 .THREAD = .{
1269 .TERMINATE = true, // for `NtCancelSynchronousIoFile`
1270 },
1271 },
1272 },
1273 &.{
1274 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
1275 .RootDirectory = null,
1276 .ObjectName = null,
1277 .Attributes = .{},
1278 .SecurityDescriptor = null,
1279 .SecurityQualityOfService = null,
1280 },
1281 &windows.teb().ClientId,
1282 ) == .SUCCESS);
1283 }
1284 defer if (builtin.target.os.tag == .windows) {
1285 windows.CloseHandle(thread.handle);
1286 };
1287
1245 {1288 {
1246 var head = t.worker_threads.load(.monotonic);1289 var head = t.worker_threads.load(.monotonic);
1247 while (true) {1290 while (true) {
...@@ -2127,26 +2170,34 @@ fn dirCreateDirWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permi...@@ -2127,26 +2170,34 @@ fn dirCreateDirWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permi
2127fn dirCreateDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void {2170fn dirCreateDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void {
2128 const t: *Threaded = @ptrCast(@alignCast(userdata));2171 const t: *Threaded = @ptrCast(@alignCast(userdata));
2129 _ = t;2172 _ = t;
2130 try Thread.checkCancel();
21312173
2132 const sub_path_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path);2174 const sub_path_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
2133 _ = permissions; // TODO use this value2175 _ = permissions; // TODO use this value
2134 const sub_dir_handle = windows.OpenFile(sub_path_w.span(), .{2176
2135 .dir = dir.handle,2177 const syscall: Syscall = try .start();
2136 .access_mask = .{2178 const sub_dir_handle = while (true) {
2137 .GENERIC = .{ .READ = true },2179 break windows.OpenFile(sub_path_w.span(), .{
2138 .STANDARD = .{ .SYNCHRONIZE = true },2180 .dir = dir.handle,
2139 },2181 .access_mask = .{
2140 .creation = .CREATE,2182 .GENERIC = .{ .READ = true },
2141 .filter = .dir_only,2183 .STANDARD = .{ .SYNCHRONIZE = true },
2142 }) catch |err| switch (err) {2184 },
2143 error.IsDir => return error.Unexpected,2185 .creation = .CREATE,
2144 error.PipeBusy => return error.Unexpected,2186 .filter = .dir_only,
2145 error.NoDevice => return error.Unexpected,2187 }) catch |err| switch (err) {
2146 error.WouldBlock => return error.Unexpected,2188 error.IsDir => return syscall.fail(error.Unexpected),
2147 error.AntivirusInterference => return error.Unexpected,2189 error.PipeBusy => return syscall.fail(error.Unexpected),
2148 else => |e| return e,2190 error.NoDevice => return syscall.fail(error.Unexpected),
2191 error.WouldBlock => return syscall.fail(error.Unexpected),
2192 error.AntivirusInterference => return syscall.fail(error.Unexpected),
2193 error.OperationCanceled => {
2194 try syscall.checkCancel();
2195 continue;
2196 },
2197 else => |e| return syscall.fail(e),
2198 };
2149 };2199 };
2200 syscall.finish();
2150 windows.CloseHandle(sub_dir_handle);2201 windows.CloseHandle(sub_dir_handle);
2151}2202}
21522203
...@@ -2225,9 +2276,7 @@ fn dirCreateDirPathOpenWindows(...@@ -2225,9 +2276,7 @@ fn dirCreateDirPathOpenWindows(
2225 .path = sub_path,2276 .path = sub_path,
2226 };2277 };
22272278
2228 while (true) {2279 components: while (true) {
2229 try Thread.checkCancel();
2230
2231 const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, component.path);2280 const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, component.path);
2232 const sub_path_w = sub_path_w_array.span();2281 const sub_path_w = sub_path_w_array.span();
2233 const is_last = it.peekNext() == null;2282 const is_last = it.peekNext() == null;
...@@ -2242,7 +2291,9 @@ fn dirCreateDirPathOpenWindows(...@@ -2242,7 +2291,9 @@ fn dirCreateDirPathOpenWindows(
2242 .Buffer = @constCast(sub_path_w.ptr),2291 .Buffer = @constCast(sub_path_w.ptr),
2243 };2292 };
2244 var io_status_block: w.IO_STATUS_BLOCK = undefined;2293 var io_status_block: w.IO_STATUS_BLOCK = undefined;
2245 const rc = w.ntdll.NtCreateFile(2294
2295 const syscall: Syscall = try .start();
2296 while (true) switch (w.ntdll.NtCreateFile(
2246 &result.handle,2297 &result.handle,
2247 .{2298 .{
2248 .SPECIFIC = .{ .FILE_DIRECTORY = .{2299 .SPECIFIC = .{ .FILE_DIRECTORY = .{
...@@ -2277,16 +2328,20 @@ fn dirCreateDirPathOpenWindows(...@@ -2277,16 +2328,20 @@ fn dirCreateDirPathOpenWindows(
2277 },2328 },
2278 null,2329 null,
2279 0,2330 0,
2280 );2331 )) {
2281
2282 switch (rc) {
2283 .SUCCESS => {2332 .SUCCESS => {
2333 syscall.finish();
2284 component = it.next() orelse return result;2334 component = it.next() orelse return result;
2285 w.CloseHandle(result.handle);2335 w.CloseHandle(result.handle);
2336 continue :components;
2337 },
2338 .CANCELLED => {
2339 try syscall.checkCancel();
2286 continue;2340 continue;
2287 },2341 },
2288 .OBJECT_NAME_INVALID => return error.BadPathName,2342 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
2289 .OBJECT_NAME_COLLISION => {2343 .OBJECT_NAME_COLLISION => {
2344 syscall.finish();
2290 assert(!is_last);2345 assert(!is_last);
2291 // stat the file and return an error if it's not a directory2346 // stat the file and return an error if it's not a directory
2292 // this is important because otherwise a dangling symlink2347 // this is important because otherwise a dangling symlink
...@@ -2297,23 +2352,24 @@ fn dirCreateDirPathOpenWindows(...@@ -2297,23 +2352,24 @@ fn dirCreateDirPathOpenWindows(
2297 if (fstat.kind != .directory) return error.NotDir;2352 if (fstat.kind != .directory) return error.NotDir;
22982353
2299 component = it.next().?;2354 component = it.next().?;
2300 continue;2355 continue :components;
2301 },2356 },
23022357
2303 .OBJECT_NAME_NOT_FOUND,2358 .OBJECT_NAME_NOT_FOUND,
2304 .OBJECT_PATH_NOT_FOUND,2359 .OBJECT_PATH_NOT_FOUND,
2305 => {2360 => {
2361 syscall.finish();
2306 component = it.previous() orelse return error.FileNotFound;2362 component = it.previous() orelse return error.FileNotFound;
2307 continue;2363 continue :components;
2308 },2364 },
23092365
2310 .NOT_A_DIRECTORY => return error.NotDir,2366 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
2311 // This can happen if the directory has 'List folder contents' permission set to 'Deny'2367 // This can happen if the directory has 'List folder contents' permission set to 'Deny'
2312 // and the directory is trying to be opened for iteration.2368 // and the directory is trying to be opened for iteration.
2313 .ACCESS_DENIED => return error.AccessDenied,2369 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
2314 .INVALID_PARAMETER => |err| return w.statusBug(err),2370 .INVALID_PARAMETER => |s| return syscall.ntstatusBug(s),
2315 else => return w.unexpectedStatus(rc),2371 else => |s| return syscall.unexpectedNtstatus(s),
2316 }2372 };
2317 }2373 }
2318}2374}
23192375
...@@ -2637,20 +2693,31 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {...@@ -2637,20 +2693,31 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
2637fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {2693fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
2638 const t: *Threaded = @ptrCast(@alignCast(userdata));2694 const t: *Threaded = @ptrCast(@alignCast(userdata));
2639 _ = t;2695 _ = t;
2640 try Thread.checkCancel();
26412696
2642 var io_status_block: windows.IO_STATUS_BLOCK = undefined;2697 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
2643 var info: windows.FILE.ALL_INFORMATION = undefined;2698 var info: windows.FILE.ALL_INFORMATION = undefined;
2644 const rc = windows.ntdll.NtQueryInformationFile(file.handle, &io_status_block, &info, @sizeOf(windows.FILE.ALL_INFORMATION), .All);2699 {
2645 switch (rc) {2700 const syscall: Syscall = try .start();
2646 .SUCCESS => {},2701 while (true) switch (windows.ntdll.NtQueryInformationFile(
2647 // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer2702 file.handle,
2648 // size provided. This is treated as success because the type of variable-length information that this would be relevant for2703 &io_status_block,
2649 // (name, volume name, etc) we don't care about.2704 &info,
2650 .BUFFER_OVERFLOW => {},2705 @sizeOf(windows.FILE.ALL_INFORMATION),
2651 .INVALID_PARAMETER => |err| return windows.statusBug(err),2706 .All,
2652 .ACCESS_DENIED => return error.AccessDenied,2707 )) {
2653 else => return windows.unexpectedStatus(rc),2708 .SUCCESS => break syscall.finish(),
2709 // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer
2710 // size provided. This is treated as success because the type of variable-length information that this would be relevant for
2711 // (name, volume name, etc) we don't care about.
2712 .BUFFER_OVERFLOW => break syscall.finish(),
2713 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),
2714 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
2715 .CANCELLED => {
2716 try syscall.checkCancel();
2717 continue;
2718 },
2719 else => |s| return syscall.unexpectedNtstatus(s),
2720 };
2654 }2721 }
2655 return .{2722 return .{
2656 .inode = info.InternalInformation.IndexNumber,2723 .inode = info.InternalInformation.IndexNumber,
...@@ -2658,15 +2725,25 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {...@@ -2658,15 +2725,25 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
2658 .permissions = .default_file,2725 .permissions = .default_file,
2659 .kind = if (info.BasicInformation.FileAttributes.REPARSE_POINT) reparse_point: {2726 .kind = if (info.BasicInformation.FileAttributes.REPARSE_POINT) reparse_point: {
2660 var tag_info: windows.FILE.ATTRIBUTE_TAG_INFO = undefined;2727 var tag_info: windows.FILE.ATTRIBUTE_TAG_INFO = undefined;
2661 const tag_rc = windows.ntdll.NtQueryInformationFile(file.handle, &io_status_block, &tag_info, @sizeOf(windows.FILE.ATTRIBUTE_TAG_INFO), .AttributeTag);2728 const syscall: Syscall = try .start();
2662 switch (tag_rc) {2729 while (true) switch (windows.ntdll.NtQueryInformationFile(
2663 .SUCCESS => {},2730 file.handle,
2731 &io_status_block,
2732 &tag_info,
2733 @sizeOf(windows.FILE.ATTRIBUTE_TAG_INFO),
2734 .AttributeTag,
2735 )) {
2736 .SUCCESS => break syscall.finish(),
2664 // INFO_LENGTH_MISMATCH and ACCESS_DENIED are the only documented possible errors2737 // INFO_LENGTH_MISMATCH and ACCESS_DENIED are the only documented possible errors
2665 // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/d295752f-ce89-4b98-8553-266d37c84f0e2738 // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/d295752f-ce89-4b98-8553-266d37c84f0e
2666 .INFO_LENGTH_MISMATCH => |err| return windows.statusBug(err),2739 .INFO_LENGTH_MISMATCH => |err| return syscall.ntstatusBug(err),
2667 .ACCESS_DENIED => return error.AccessDenied,2740 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
2668 else => return windows.unexpectedStatus(rc),2741 .CANCELLED => {
2669 }2742 try syscall.checkCancel();
2743 continue;
2744 },
2745 else => |s| return syscall.unexpectedNtstatus(s),
2746 };
2670 if (tag_info.ReparseTag.IsSurrogate) break :reparse_point .sym_link;2747 if (tag_info.ReparseTag.IsSurrogate) break :reparse_point .sym_link;
2671 // Unknown reparse point2748 // Unknown reparse point
2672 break :reparse_point .unknown;2749 break :reparse_point .unknown;
...@@ -2853,7 +2930,6 @@ fn dirAccessWindows(...@@ -2853,7 +2930,6 @@ fn dirAccessWindows(
2853) Dir.AccessError!void {2930) Dir.AccessError!void {
2854 const t: *Threaded = @ptrCast(@alignCast(userdata));2931 const t: *Threaded = @ptrCast(@alignCast(userdata));
2855 _ = t;2932 _ = t;
2856 try Thread.checkCancel();
28572933
2858 _ = options; // TODO2934 _ = options; // TODO
28592935
...@@ -2879,16 +2955,21 @@ fn dirAccessWindows(...@@ -2879,16 +2955,21 @@ fn dirAccessWindows(
2879 .SecurityQualityOfService = null,2955 .SecurityQualityOfService = null,
2880 };2956 };
2881 var basic_info: windows.FILE.BASIC_INFORMATION = undefined;2957 var basic_info: windows.FILE.BASIC_INFORMATION = undefined;
2882 switch (windows.ntdll.NtQueryAttributesFile(&attr, &basic_info)) {2958 const syscall: Syscall = try .start();
2883 .SUCCESS => return,2959 while (true) switch (windows.ntdll.NtQueryAttributesFile(&attr, &basic_info)) {
2884 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,2960 .SUCCESS => return syscall.finish(),
2885 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,2961 .CANCELLED => {
2886 .OBJECT_NAME_INVALID => |err| return windows.statusBug(err),2962 try syscall.checkCancel();
2887 .INVALID_PARAMETER => |err| return windows.statusBug(err),2963 continue;
2888 .ACCESS_DENIED => return error.AccessDenied,2964 },
2889 .OBJECT_PATH_SYNTAX_BAD => |err| return windows.statusBug(err),2965 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
2890 else => |rc| return windows.unexpectedStatus(rc),2966 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
2891 }2967 .OBJECT_NAME_INVALID => |err| return syscall.ntstatusBug(err),
2968 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),
2969 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
2970 .OBJECT_PATH_SYNTAX_BAD => |err| return syscall.ntstatusBug(err),
2971 else => |rc| return syscall.unexpectedNtstatus(rc),
2972 };
2892}2973}
28932974
2894const dirCreateFile = switch (native_os) {2975const dirCreateFile = switch (native_os) {
...@@ -3071,27 +3152,40 @@ fn dirCreateFileWindows(...@@ -3071,27 +3152,40 @@ fn dirCreateFileWindows(
3071 const w = windows;3152 const w = windows;
3072 const t: *Threaded = @ptrCast(@alignCast(userdata));3153 const t: *Threaded = @ptrCast(@alignCast(userdata));
3073 _ = t;3154 _ = t;
3074 try Thread.checkCancel();
30753155
3076 const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, sub_path);3156 const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, sub_path);
3077 const sub_path_w = sub_path_w_array.span();3157 const sub_path_w = sub_path_w_array.span();
30783158
3079 const handle = try w.OpenFile(sub_path_w, .{3159 const handle = handle: {
3080 .dir = dir.handle,3160 const syscall: Syscall = try .start();
3081 .access_mask = .{3161 while (true) {
3082 .STANDARD = .{ .SYNCHRONIZE = true },3162 if (w.OpenFile(sub_path_w, .{
3083 .GENERIC = .{3163 .dir = dir.handle,
3084 .WRITE = true,3164 .access_mask = .{
3085 .READ = flags.read,3165 .STANDARD = .{ .SYNCHRONIZE = true },
3086 },3166 .GENERIC = .{
3087 },3167 .WRITE = true,
3088 .creation = if (flags.exclusive)3168 .READ = flags.read,
3089 .CREATE3169 },
3090 else if (flags.truncate)3170 },
3091 .OVERWRITE_IF3171 .creation = if (flags.exclusive)
3092 else3172 .CREATE
3093 .OPEN_IF,3173 else if (flags.truncate)
3094 });3174 .OVERWRITE_IF
3175 else
3176 .OPEN_IF,
3177 })) |handle| {
3178 syscall.finish();
3179 break :handle handle;
3180 } else |err| switch (err) {
3181 error.OperationCanceled => {
3182 try syscall.checkCancel();
3183 continue;
3184 },
3185 else => |e| return syscall.fail(e),
3186 }
3187 }
3188 };
3095 errdefer w.CloseHandle(handle);3189 errdefer w.CloseHandle(handle);
30963190
3097 var io_status_block: w.IO_STATUS_BLOCK = undefined;3191 var io_status_block: w.IO_STATUS_BLOCK = undefined;
...@@ -3100,7 +3194,8 @@ fn dirCreateFileWindows(...@@ -3100,7 +3194,8 @@ fn dirCreateFileWindows(
3100 .shared => false,3194 .shared => false,
3101 .exclusive => true,3195 .exclusive => true,
3102 };3196 };
3103 const status = w.ntdll.NtLockFile(3197 const syscall: Syscall = try .start();
3198 while (true) switch (w.ntdll.NtLockFile(
3104 handle,3199 handle,
3105 null,3200 null,
3106 null,3201 null,
...@@ -3111,16 +3206,16 @@ fn dirCreateFileWindows(...@@ -3111,16 +3206,16 @@ fn dirCreateFileWindows(
3111 null,3206 null,
3112 @intFromBool(flags.lock_nonblocking),3207 @intFromBool(flags.lock_nonblocking),
3113 @intFromBool(exclusive),3208 @intFromBool(exclusive),
3114 );3209 )) {
3115 switch (status) {3210 .SUCCESS => {
3116 .SUCCESS => {},3211 syscall.finish();
3117 .INSUFFICIENT_RESOURCES => return error.SystemResources,3212 return .{ .handle = handle };
3118 .LOCK_NOT_GRANTED => return error.WouldBlock,3213 },
3119 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer3214 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
3120 else => return windows.unexpectedStatus(status),3215 .LOCK_NOT_GRANTED => return syscall.fail(error.WouldBlock),
3121 }3216 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer
31223217 else => |status| return syscall.unexpectedNtstatus(status),
3123 return .{ .handle = handle };3218 };
3124}3219}
31253220
3126fn dirCreateFileWasi(3221fn dirCreateFileWasi(
...@@ -3399,14 +3494,14 @@ fn dirOpenFileWindows(...@@ -3399,14 +3494,14 @@ fn dirOpenFileWindows(
3399 flags: File.OpenFlags,3494 flags: File.OpenFlags,
3400) File.OpenError!File {3495) File.OpenError!File {
3401 const t: *Threaded = @ptrCast(@alignCast(userdata));3496 const t: *Threaded = @ptrCast(@alignCast(userdata));
3497 _ = t;
3402 const sub_path_w_array = try windows.sliceToPrefixedFileW(dir.handle, sub_path);3498 const sub_path_w_array = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
3403 const sub_path_w = sub_path_w_array.span();3499 const sub_path_w = sub_path_w_array.span();
3404 const dir_handle = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle;3500 const dir_handle = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle;
3405 return dirOpenFileWtf16(t, dir_handle, sub_path_w, flags);3501 return dirOpenFileWtf16(dir_handle, sub_path_w, flags);
3406}3502}
34073503
3408pub fn dirOpenFileWtf16(3504pub fn dirOpenFileWtf16(
3409 t: *Threaded,
3410 dir_handle: ?windows.HANDLE,3505 dir_handle: ?windows.HANDLE,
3411 sub_path_w: [:0]const u16,3506 sub_path_w: [:0]const u16,
3412 flags: File.OpenFlags,3507 flags: File.OpenFlags,
...@@ -3415,7 +3510,6 @@ pub fn dirOpenFileWtf16(...@@ -3415,7 +3510,6 @@ pub fn dirOpenFileWtf16(
3415 if (!allow_directory and std.mem.eql(u16, sub_path_w, &.{'.'})) return error.IsDir;3510 if (!allow_directory and std.mem.eql(u16, sub_path_w, &.{'.'})) return error.IsDir;
3416 if (!allow_directory and std.mem.eql(u16, sub_path_w, &.{ '.', '.' })) return error.IsDir;3511 if (!allow_directory and std.mem.eql(u16, sub_path_w, &.{ '.', '.' })) return error.IsDir;
3417 const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong;3512 const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong;
3418 _ = t;
3419 const w = windows;3513 const w = windows;
34203514
3421 var nt_name: w.UNICODE_STRING = .{3515 var nt_name: w.UNICODE_STRING = .{
...@@ -3437,11 +3531,10 @@ pub fn dirOpenFileWtf16(...@@ -3437,11 +3531,10 @@ pub fn dirOpenFileWtf16(
3437 const max_attempts = 13;3531 const max_attempts = 13;
3438 var attempt: u5 = 0;3532 var attempt: u5 = 0;
34393533
3534 var syscall: Syscall = try .start();
3440 const handle = while (true) {3535 const handle = while (true) {
3441 try Thread.checkCancel();
3442
3443 var result: w.HANDLE = undefined;3536 var result: w.HANDLE = undefined;
3444 const rc = w.ntdll.NtCreateFile(3537 switch (w.ntdll.NtCreateFile(
3445 &result,3538 &result,
3446 .{3539 .{
3447 .STANDARD = .{ .SYNCHRONIZE = true },3540 .STANDARD = .{ .SYNCHRONIZE = true },
...@@ -3463,49 +3556,59 @@ pub fn dirOpenFileWtf16(...@@ -3463,49 +3556,59 @@ pub fn dirOpenFileWtf16(
3463 },3556 },
3464 null,3557 null,
3465 0,3558 0,
3466 );3559 )) {
3467 switch (rc) {3560 .SUCCESS => {
3468 .SUCCESS => break result,3561 syscall.finish();
3469 .OBJECT_NAME_INVALID => return error.BadPathName,3562 break result;
3470 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,3563 },
3471 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,3564 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
3472 .BAD_NETWORK_PATH => return error.NetworkNotFound, // \\server was not found3565 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
3473 .BAD_NETWORK_NAME => return error.NetworkNotFound, // \\server was found but \\server\share wasn't3566 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
3474 .NO_MEDIA_IN_DEVICE => return error.NoDevice,3567 .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found
3475 .INVALID_PARAMETER => |err| return w.statusBug(err),3568 .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't
3569 .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice),
3570 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),
3571 .CANCELLED => {
3572 try syscall.checkCancel();
3573 continue;
3574 },
3476 .SHARING_VIOLATION => {3575 .SHARING_VIOLATION => {
3477 // This occurs if the file attempting to be opened is a running3576 // This occurs if the file attempting to be opened is a running
3478 // executable. However, there's a kernel bug: the error may be3577 // executable. However, there's a kernel bug: the error may be
3479 // incorrectly returned for an indeterminate amount of time3578 // incorrectly returned for an indeterminate amount of time
3480 // after an executable file is closed. Here we work around the3579 // after an executable file is closed. Here we work around the
3481 // kernel bug with retry attempts.3580 // kernel bug with retry attempts.
3581 syscall.finish();
3482 if (max_attempts - attempt == 0) return error.SharingViolation;3582 if (max_attempts - attempt == 0) return error.SharingViolation;
3483 _ = w.kernel32.SleepEx((@as(u32, 1) << attempt) >> 1, w.TRUE);3583 _ = w.kernel32.SleepEx((@as(u32, 1) << attempt) >> 1, w.TRUE);
3484 attempt += 1;3584 attempt += 1;
3585 syscall = try .start();
3485 continue;3586 continue;
3486 },3587 },
3487 .ACCESS_DENIED => return error.AccessDenied,3588 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
3488 .PIPE_BUSY => return error.PipeBusy,3589 .PIPE_BUSY => return syscall.fail(error.PipeBusy),
3489 .PIPE_NOT_AVAILABLE => return error.NoDevice,3590 .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice),
3490 .OBJECT_PATH_SYNTAX_BAD => |err| return w.statusBug(err),3591 .OBJECT_PATH_SYNTAX_BAD => |err| return syscall.ntstatusBug(err),
3491 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,3592 .OBJECT_NAME_COLLISION => return syscall.fail(error.PathAlreadyExists),
3492 .FILE_IS_A_DIRECTORY => return error.IsDir,3593 .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir),
3493 .NOT_A_DIRECTORY => return error.NotDir,3594 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
3494 .USER_MAPPED_FILE => return error.AccessDenied,3595 .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied),
3495 .INVALID_HANDLE => |err| return w.statusBug(err),3596 .INVALID_HANDLE => |err| return syscall.ntstatusBug(err),
3496 .DELETE_PENDING => {3597 .DELETE_PENDING => {
3497 // This error means that there *was* a file in this location on3598 // This error means that there *was* a file in this location on
3498 // the file system, but it was deleted. However, the OS is not3599 // the file system, but it was deleted. However, the OS is not
3499 // finished with the deletion operation, and so this CreateFile3600 // finished with the deletion operation, and so this CreateFile
3500 // call has failed. Here, we simulate the kernel bug being3601 // call has failed. Here, we simulate the kernel bug being
3501 // fixed by sleeping and retrying until the error goes away.3602 // fixed by sleeping and retrying until the error goes away.
3603 syscall.finish();
3502 if (max_attempts - attempt == 0) return error.SharingViolation;3604 if (max_attempts - attempt == 0) return error.SharingViolation;
3503 _ = w.kernel32.SleepEx((@as(u32, 1) << attempt) >> 1, w.TRUE);3605 _ = w.kernel32.SleepEx((@as(u32, 1) << attempt) >> 1, w.TRUE);
3504 attempt += 1;3606 attempt += 1;
3607 syscall = try .start();
3505 continue;3608 continue;
3506 },3609 },
3507 .VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference,3610 .VIRUS_INFECTED, .VIRUS_DELETED => return syscall.fail(error.AntivirusInterference),
3508 else => return w.unexpectedStatus(rc),3611 else => |rc| return syscall.unexpectedNtstatus(rc),
3509 }3612 }
3510 };3613 };
3511 errdefer w.CloseHandle(handle);3614 errdefer w.CloseHandle(handle);
...@@ -3515,7 +3618,8 @@ pub fn dirOpenFileWtf16(...@@ -3515,7 +3618,8 @@ pub fn dirOpenFileWtf16(
3515 .shared => false,3618 .shared => false,
3516 .exclusive => true,3619 .exclusive => true,
3517 };3620 };
3518 const status = w.ntdll.NtLockFile(3621 syscall = try .start();
3622 while (true) switch (w.ntdll.NtLockFile(
3519 handle,3623 handle,
3520 null,3624 null,
3521 null,3625 null,
...@@ -3526,14 +3630,13 @@ pub fn dirOpenFileWtf16(...@@ -3526,14 +3630,13 @@ pub fn dirOpenFileWtf16(
3526 null,3630 null,
3527 @intFromBool(flags.lock_nonblocking),3631 @intFromBool(flags.lock_nonblocking),
3528 @intFromBool(exclusive),3632 @intFromBool(exclusive),
3529 );3633 )) {
3530 switch (status) {3634 .SUCCESS => break syscall.finish(),
3531 .SUCCESS => {},3635 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
3532 .INSUFFICIENT_RESOURCES => return error.SystemResources,3636 .LOCK_NOT_GRANTED => return syscall.fail(error.WouldBlock),
3533 .LOCK_NOT_GRANTED => return error.WouldBlock,3637 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer
3534 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer3638 else => |status| return syscall.unexpectedNtstatus(status),
3535 else => return windows.unexpectedStatus(status),3639 };
3536 }
3537 return .{ .handle = handle };3640 return .{ .handle = handle };
3538}3641}
35393642
...@@ -3773,8 +3876,9 @@ pub fn dirOpenDirWindows(...@@ -3773,8 +3876,9 @@ pub fn dirOpenDirWindows(
3773 };3876 };
3774 var io_status_block: w.IO_STATUS_BLOCK = undefined;3877 var io_status_block: w.IO_STATUS_BLOCK = undefined;
3775 var result: Dir = .{ .handle = undefined };3878 var result: Dir = .{ .handle = undefined };
3776 try Thread.checkCancel();3879
3777 const rc = w.ntdll.NtCreateFile(3880 const syscall: Syscall = try .start();
3881 while (true) switch (w.ntdll.NtCreateFile(
3778 &result.handle,3882 &result.handle,
3779 // TODO remove some of these flags if options.access_sub_paths is false3883 // TODO remove some of these flags if options.access_sub_paths is false
3780 .{3884 .{
...@@ -3810,21 +3914,26 @@ pub fn dirOpenDirWindows(...@@ -3810,21 +3914,26 @@ pub fn dirOpenDirWindows(
3810 },3914 },
3811 null,3915 null,
3812 0,3916 0,
3813 );3917 )) {
38143918 .SUCCESS => {
3815 switch (rc) {3919 syscall.finish();
3816 .SUCCESS => return result,3920 return result;
3817 .OBJECT_NAME_INVALID => return error.BadPathName,3921 },
3818 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,3922 .CANCELLED => {
3923 try syscall.checkCancel();
3924 continue;
3925 },
3926 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
3927 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
3819 .OBJECT_NAME_COLLISION => |err| return w.statusBug(err),3928 .OBJECT_NAME_COLLISION => |err| return w.statusBug(err),
3820 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,3929 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
3821 .NOT_A_DIRECTORY => return error.NotDir,3930 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
3822 // This can happen if the directory has 'List folder contents' permission set to 'Deny'3931 // This can happen if the directory has 'List folder contents' permission set to 'Deny'
3823 // and the directory is trying to be opened for iteration.3932 // and the directory is trying to be opened for iteration.
3824 .ACCESS_DENIED => return error.AccessDenied,3933 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
3825 .INVALID_PARAMETER => |err| return w.statusBug(err),3934 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),
3826 else => return w.unexpectedStatus(rc),3935 else => |rc| return syscall.unexpectedNtstatus(rc),
3827 }3936 };
3828}3937}
38293938
3830fn dirClose(userdata: ?*anyopaque, dirs: []const Dir) void {3939fn dirClose(userdata: ?*anyopaque, dirs: []const Dir) void {
...@@ -4264,9 +4373,9 @@ fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) D...@@ -4264,9 +4373,9 @@ fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) D
4264 // buffered data.4373 // buffered data.
4265 if (buffer_index != 0) break;4374 if (buffer_index != 0) break;
42664375
4267 try Thread.checkCancel();
4268 var io_status_block: w.IO_STATUS_BLOCK = undefined;4376 var io_status_block: w.IO_STATUS_BLOCK = undefined;
4269 const rc = w.ntdll.NtQueryDirectoryFile(4377 const syscall: Syscall = try .start();
4378 const rc = while (true) switch (w.ntdll.NtQueryDirectoryFile(
4270 dr.dir.handle,4379 dr.dir.handle,
4271 null,4380 null,
4272 null,4381 null,
...@@ -4278,7 +4387,16 @@ fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) D...@@ -4278,7 +4387,16 @@ fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) D
4278 w.FALSE,4387 w.FALSE,
4279 null,4388 null,
4280 @intFromBool(dr.state == .reset),4389 @intFromBool(dr.state == .reset),
4281 );4390 )) {
4391 .CANCELLED => {
4392 try syscall.checkCancel();
4393 continue;
4394 },
4395 else => |rc| {
4396 syscall.finish();
4397 break rc;
4398 },
4399 };
4282 dr.state = .reading;4400 dr.state = .reading;
4283 if (io_status_block.Information == 0) {4401 if (io_status_block.Information == 0) {
4284 dr.state = .finished;4402 dr.state = .finished;
...@@ -4466,32 +4584,40 @@ fn dirRealPathFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8,...@@ -4466,32 +4584,40 @@ fn dirRealPathFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8,
4466 const t: *Threaded = @ptrCast(@alignCast(userdata));4584 const t: *Threaded = @ptrCast(@alignCast(userdata));
4467 _ = t;4585 _ = t;
44684586
4469 try Thread.checkCancel();
4470
4471 var path_name_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path);4587 var path_name_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
44724588
4473 const h_file = blk: {4589 const h_file = handle: {
4474 const res = windows.OpenFile(path_name_w.span(), .{4590 const syscall: Syscall = try .start();
4475 .dir = dir.handle,4591 while (true) {
4476 .access_mask = .{4592 if (windows.OpenFile(path_name_w.span(), .{
4477 .GENERIC = .{ .READ = true },4593 .dir = dir.handle,
4478 .STANDARD = .{ .SYNCHRONIZE = true },4594 .access_mask = .{
4479 },4595 .GENERIC = .{ .READ = true },
4480 .creation = .OPEN,4596 .STANDARD = .{ .SYNCHRONIZE = true },
4481 .filter = .any,4597 },
4482 }) catch |err| switch (err) {4598 .creation = .OPEN,
4483 error.WouldBlock => unreachable,4599 .filter = .any,
4484 else => |e| return e,4600 })) |handle| {
4485 };4601 syscall.finish();
4486 break :blk res;4602 break :handle handle;
4603 } else |err| switch (err) {
4604 error.WouldBlock => unreachable,
4605 error.OperationCanceled => {
4606 try syscall.checkCancel();
4607 continue;
4608 },
4609 else => |e| return syscall.fail(e),
4610 }
4611 }
4487 };4612 };
4488 defer windows.CloseHandle(h_file);4613 defer windows.CloseHandle(h_file);
4489 return realPathWindows(h_file, out_buffer);4614 return realPathWindows(h_file, out_buffer);
4490}4615}
44914616
4492fn realPathWindows(h_file: windows.HANDLE, out_buffer: []u8) File.RealPathError!usize {4617fn realPathWindows(h_file: windows.HANDLE, out_buffer: []u8) File.RealPathError!usize {
4493 // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks
4494 var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;4618 var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
4619 // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks
4620 try Thread.checkCancel();
4495 const wide_slice = try windows.GetFinalPathNameByHandle(h_file, .{}, &wide_buf);4621 const wide_slice = try windows.GetFinalPathNameByHandle(h_file, .{}, &wide_buf);
44964622
4497 const len = std.unicode.calcWtf8Len(wide_slice);4623 const len = std.unicode.calcWtf8Len(wide_slice);
...@@ -4885,8 +5011,6 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov...@@ -4885,8 +5011,6 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov
4885 _ = t;5011 _ = t;
4886 const w = windows;5012 const w = windows;
48875013
4888 try Thread.checkCancel();
4889
4890 const sub_path_w_buf = try w.sliceToPrefixedFileW(dir.handle, sub_path);5014 const sub_path_w_buf = try w.sliceToPrefixedFileW(dir.handle, sub_path);
4891 const sub_path_w = sub_path_w_buf.span();5015 const sub_path_w = sub_path_w_buf.span();
48925016
...@@ -4909,47 +5033,49 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov...@@ -4909,47 +5033,49 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov
49095033
4910 var io_status_block: w.IO_STATUS_BLOCK = undefined;5034 var io_status_block: w.IO_STATUS_BLOCK = undefined;
4911 var tmp_handle: w.HANDLE = undefined;5035 var tmp_handle: w.HANDLE = undefined;
4912 var rc = w.ntdll.NtCreateFile(5036 {
4913 &tmp_handle,5037 const syscall: Syscall = try .start();
4914 .{ .STANDARD = .{5038 while (true) switch (w.ntdll.NtCreateFile(
4915 .RIGHTS = .{ .DELETE = true },5039 &tmp_handle,
4916 .SYNCHRONIZE = true,5040 .{ .STANDARD = .{
4917 } },5041 .RIGHTS = .{ .DELETE = true },
4918 &.{5042 .SYNCHRONIZE = true,
4919 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),5043 } },
4920 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,5044 &.{
4921 .Attributes = .{},5045 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
4922 .ObjectName = &nt_name,5046 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
4923 .SecurityDescriptor = null,5047 .Attributes = .{},
4924 .SecurityQualityOfService = null,5048 .ObjectName = &nt_name,
4925 },5049 .SecurityDescriptor = null,
4926 &io_status_block,5050 .SecurityQualityOfService = null,
4927 null,5051 },
4928 .{},5052 &io_status_block,
4929 .VALID_FLAGS,5053 null,
4930 .OPEN,5054 .{},
4931 .{5055 .VALID_FLAGS,
4932 .DIRECTORY_FILE = remove_dir,5056 .OPEN,
4933 .NON_DIRECTORY_FILE = !remove_dir,5057 .{
4934 .OPEN_REPARSE_POINT = true, // would we ever want to delete the target instead?5058 .DIRECTORY_FILE = remove_dir,
4935 },5059 .NON_DIRECTORY_FILE = !remove_dir,
4936 null,5060 .OPEN_REPARSE_POINT = true, // would we ever want to delete the target instead?
4937 0,5061 },
4938 );5062 null,
4939 switch (rc) {5063 0,
4940 .SUCCESS => {},5064 )) {
4941 .OBJECT_NAME_INVALID => |err| return w.statusBug(err),5065 .SUCCESS => break syscall.finish(),
4942 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,5066 .OBJECT_NAME_INVALID => |err| return syscall.ntstatusBug(err),
4943 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,5067 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
4944 .BAD_NETWORK_PATH => return error.NetworkNotFound, // \\server was not found5068 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
4945 .BAD_NETWORK_NAME => return error.NetworkNotFound, // \\server was found but \\server\share wasn't5069 .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found
4946 .INVALID_PARAMETER => |err| return w.statusBug(err),5070 .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't
4947 .FILE_IS_A_DIRECTORY => return error.IsDir,5071 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),
4948 .NOT_A_DIRECTORY => return error.NotDir,5072 .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir),
4949 .SHARING_VIOLATION => return error.FileBusy,5073 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
4950 .ACCESS_DENIED => return error.AccessDenied,5074 .SHARING_VIOLATION => return syscall.fail(error.FileBusy),
4951 .DELETE_PENDING => return,5075 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
4952 else => return w.unexpectedStatus(rc),5076 .DELETE_PENDING => return syscall.finish(),
5077 else => |rc| return syscall.unexpectedNtstatus(rc),
5078 };
4953 }5079 }
4954 defer w.CloseHandle(tmp_handle);5080 defer w.CloseHandle(tmp_handle);
49555081
...@@ -4964,9 +5090,7 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov...@@ -4964,9 +5090,7 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov
4964 //5090 //
4965 // The strategy here is just to try using FileDispositionInformationEx and fall back to5091 // The strategy here is just to try using FileDispositionInformationEx and fall back to
4966 // FileDispositionInformation if the return value lets us know that some aspect of it is not supported.5092 // FileDispositionInformation if the return value lets us know that some aspect of it is not supported.
4967 const need_fallback = need_fallback: {5093 const rc = rc: {
4968 try Thread.checkCancel();
4969
4970 // Deletion with posix semantics if the filesystem supports it.5094 // Deletion with posix semantics if the filesystem supports it.
4971 const info: w.FILE.DISPOSITION.INFORMATION.EX = .{ .Flags = .{5095 const info: w.FILE.DISPOSITION.INFORMATION.EX = .{ .Flags = .{
4972 .DELETE = true,5096 .DELETE = true,
...@@ -4974,29 +5098,32 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov...@@ -4974,29 +5098,32 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov
4974 .IGNORE_READONLY_ATTRIBUTE = true,5098 .IGNORE_READONLY_ATTRIBUTE = true,
4975 } };5099 } };
49765100
4977 rc = w.ntdll.NtSetInformationFile(5101 const syscall: Syscall = try .start();
5102 while (true) switch (w.ntdll.NtSetInformationFile(
4978 tmp_handle,5103 tmp_handle,
4979 &io_status_block,5104 &io_status_block,
4980 &info,5105 &info,
4981 @sizeOf(w.FILE.DISPOSITION.INFORMATION.EX),5106 @sizeOf(w.FILE.DISPOSITION.INFORMATION.EX),
4982 .DispositionEx,5107 .DispositionEx,
4983 );5108 )) {
4984 switch (rc) {5109 .CANCELLED => {
4985 .SUCCESS => return,5110 try syscall.checkCancel();
5111 continue;
5112 },
4986 // The filesystem does not support FileDispositionInformationEx5113 // The filesystem does not support FileDispositionInformationEx
4987 .INVALID_PARAMETER,5114 .INVALID_PARAMETER,
4988 // The operating system does not support FileDispositionInformationEx5115 // The operating system does not support FileDispositionInformationEx
4989 .INVALID_INFO_CLASS,5116 .INVALID_INFO_CLASS,
4990 // The operating system does not support one of the flags5117 // The operating system does not support one of the flags
4991 .NOT_SUPPORTED,5118 .NOT_SUPPORTED,
4992 => break :need_fallback true,5119 => break, // use fallback path below; `syscall` still active
4993 // For all other statuses, fall down to the switch below to handle them.
4994 else => break :need_fallback false,
4995 }
4996 };
49975120
4998 if (need_fallback) {5121 // For all other statuses, fall down to the switch below to handle them.
4999 try Thread.checkCancel();5122 else => |rc| {
5123 syscall.finish();
5124 break :rc rc;
5125 },
5126 };
50005127
5001 // Deletion with file pending semantics, which requires waiting or moving5128 // Deletion with file pending semantics, which requires waiting or moving
5002 // files to get them removed (from here).5129 // files to get them removed (from here).
...@@ -5004,14 +5131,23 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov...@@ -5004,14 +5131,23 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov
5004 .DeleteFile = w.TRUE,5131 .DeleteFile = w.TRUE,
5005 };5132 };
50065133
5007 rc = w.ntdll.NtSetInformationFile(5134 while (true) switch (w.ntdll.NtSetInformationFile(
5008 tmp_handle,5135 tmp_handle,
5009 &io_status_block,5136 &io_status_block,
5010 &file_dispo,5137 &file_dispo,
5011 @sizeOf(w.FILE.DISPOSITION.INFORMATION),5138 @sizeOf(w.FILE.DISPOSITION.INFORMATION),
5012 .Disposition,5139 .Disposition,
5013 );5140 )) {
5014 }5141 .CANCELLED => {
5142 try syscall.checkCancel();
5143 continue;
5144 },
5145 else => |rc| {
5146 syscall.finish();
5147 break :rc rc;
5148 },
5149 };
5150 };
5015 switch (rc) {5151 switch (rc) {
5016 .SUCCESS => {},5152 .SUCCESS => {},
5017 .DIRECTORY_NOT_EMPTY => return error.DirNotEmpty,5153 .DIRECTORY_NOT_EMPTY => return error.DirNotEmpty,
...@@ -5135,23 +5271,33 @@ fn dirRenameWindows(...@@ -5135,23 +5271,33 @@ fn dirRenameWindows(
5135 const new_path_w = new_path_w_buf.span();5271 const new_path_w = new_path_w_buf.span();
5136 const replace_if_exists = true;5272 const replace_if_exists = true;
51375273
5138 try Thread.checkCancel();5274 const src_fd = src_fd: {
51395275 const syscall: Syscall = try .start();
5140 const src_fd = w.OpenFile(old_path_w, .{5276 while (true) {
5141 .dir = old_dir.handle,5277 if (w.OpenFile(old_path_w, .{
5142 .access_mask = .{5278 .dir = old_dir.handle,
5143 .GENERIC = .{ .WRITE = true },5279 .access_mask = .{
5144 .STANDARD = .{5280 .GENERIC = .{ .WRITE = true },
5145 .RIGHTS = .{ .DELETE = true },5281 .STANDARD = .{
5146 .SYNCHRONIZE = true,5282 .RIGHTS = .{ .DELETE = true },
5147 },5283 .SYNCHRONIZE = true,
5148 },5284 },
5149 .creation = .OPEN,5285 },
5150 .filter = .any, // This function is supposed to rename both files and directories.5286 .creation = .OPEN,
5151 .follow_symlinks = false,5287 .filter = .any, // This function is supposed to rename both files and directories.
5152 }) catch |err| switch (err) {5288 .follow_symlinks = false,
5153 error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`.5289 })) |handle| {
5154 else => |e| return e,5290 syscall.finish();
5291 break :src_fd handle;
5292 } else |err| switch (err) {
5293 error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`.
5294 error.OperationCanceled => {
5295 try syscall.checkCancel();
5296 continue;
5297 },
5298 else => |e| return e,
5299 }
5300 }
5155 };5301 };
5156 defer w.CloseHandle(src_fd);5302 defer w.CloseHandle(src_fd);
51575303
...@@ -5354,8 +5500,6 @@ fn dirSymLinkWindows(...@@ -5354,8 +5500,6 @@ fn dirSymLinkWindows(
5354 _ = t;5500 _ = t;
5355 const w = windows;5501 const w = windows;
53565502
5357 try Thread.checkCancel();
5358
5359 // Target path does not use sliceToPrefixedFileW because certain paths5503 // Target path does not use sliceToPrefixedFileW because certain paths
5360 // are handled differently when creating a symlink than they would be5504 // are handled differently when creating a symlink than they would be
5361 // when converting to an NT namespaced path. CreateSymbolicLink in5505 // when converting to an NT namespaced path. CreateSymbolicLink in
...@@ -5385,22 +5529,34 @@ fn dirSymLinkWindows(...@@ -5385,22 +5529,34 @@ fn dirSymLinkWindows(
5385 Flags: w.ULONG,5529 Flags: w.ULONG,
5386 };5530 };
53875531
5388 const symlink_handle = w.OpenFile(sym_link_path_w.span(), .{5532 const symlink_handle = handle: {
5389 .access_mask = .{5533 const syscall: Syscall = try .start();
5390 .GENERIC = .{ .READ = true, .WRITE = true },5534 while (true) {
5391 .STANDARD = .{ .SYNCHRONIZE = true },5535 if (w.OpenFile(sym_link_path_w.span(), .{
5392 },5536 .access_mask = .{
5393 .dir = dir.handle,5537 .GENERIC = .{ .READ = true, .WRITE = true },
5394 .creation = .CREATE,5538 .STANDARD = .{ .SYNCHRONIZE = true },
5395 .filter = if (flags.is_directory) .dir_only else .non_directory_only,5539 },
5396 }) catch |err| switch (err) {5540 .dir = dir.handle,
5397 error.IsDir => return error.PathAlreadyExists,5541 .creation = .CREATE,
5398 error.NotDir => return error.Unexpected,5542 .filter = if (flags.is_directory) .dir_only else .non_directory_only,
5399 error.WouldBlock => return error.Unexpected,5543 })) |handle| {
5400 error.PipeBusy => return error.Unexpected,5544 syscall.finish();
5401 error.NoDevice => return error.Unexpected,5545 break :handle handle;
5402 error.AntivirusInterference => return error.Unexpected,5546 } else |err| switch (err) {
5403 else => |e| return e,5547 error.IsDir => return syscall.fail(error.PathAlreadyExists),
5548 error.NotDir => return syscall.fail(error.Unexpected),
5549 error.WouldBlock => return syscall.fail(error.Unexpected),
5550 error.PipeBusy => return syscall.fail(error.Unexpected),
5551 error.NoDevice => return syscall.fail(error.Unexpected),
5552 error.AntivirusInterference => return syscall.fail(error.Unexpected),
5553 error.OperationCanceled => {
5554 try syscall.checkCancel();
5555 continue;
5556 },
5557 else => |e| return e,
5558 }
5559 }
5404 };5560 };
5405 defer w.CloseHandle(symlink_handle);5561 defer w.CloseHandle(symlink_handle);
54065562
...@@ -5576,11 +5732,21 @@ fn dirReadLinkWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buf...@@ -5576,11 +5732,21 @@ fn dirReadLinkWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buf
5576 _ = t;5732 _ = t;
5577 const w = windows;5733 const w = windows;
55785734
5579 try Thread.checkCancel();
5580
5581 var sub_path_w_buf = try windows.sliceToPrefixedFileW(dir.handle, sub_path);5735 var sub_path_w_buf = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
55825736
5583 const result_w = try w.ReadLink(dir.handle, sub_path_w_buf.span(), &sub_path_w_buf.data);5737 const syscall: Syscall = try .start();
5738 const result_w = while (true) {
5739 if (w.ReadLink(dir.handle, sub_path_w_buf.span(), &sub_path_w_buf.data)) |res| {
5740 syscall.finish();
5741 break res;
5742 } else |err| switch (err) {
5743 error.OperationCanceled => {
5744 try syscall.checkCancel();
5745 continue;
5746 },
5747 else => |e| return syscall.fail(e),
5748 }
5749 };
55845750
5585 const len = std.unicode.calcWtf8Len(result_w);5751 const len = std.unicode.calcWtf8Len(result_w);
5586 if (len > buffer.len) return error.NameTooLong;5752 if (len > buffer.len) return error.NameTooLong;
...@@ -5997,17 +6163,25 @@ fn fileSyncWindows(userdata: ?*anyopaque, file: File) File.SyncError!void {...@@ -5997,17 +6163,25 @@ fn fileSyncWindows(userdata: ?*anyopaque, file: File) File.SyncError!void {
5997 const t: *Threaded = @ptrCast(@alignCast(userdata));6163 const t: *Threaded = @ptrCast(@alignCast(userdata));
5998 _ = t;6164 _ = t;
59996165
6000 try Thread.checkCancel();6166 const syscall: Syscall = try .start();
60016167 while (true) {
6002 if (windows.kernel32.FlushFileBuffers(file.handle) != 0)6168 if (windows.kernel32.FlushFileBuffers(file.handle) != 0) {
6003 return;6169 return syscall.finish();
60046170 }
6005 switch (windows.GetLastError()) {6171 switch (windows.GetLastError()) {
6006 .SUCCESS => return,6172 .SUCCESS => unreachable, // `FlushFileBuffers` returned nonzero
6007 .INVALID_HANDLE => unreachable,6173 .INVALID_HANDLE => unreachable,
6008 .ACCESS_DENIED => return error.AccessDenied, // a sync was performed but the system couldn't update the access time6174 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), // a sync was performed but the system couldn't update the access time
6009 .UNEXP_NET_ERR => return error.InputOutput,6175 .UNEXP_NET_ERR => return syscall.fail(error.InputOutput),
6010 else => |err| return windows.unexpectedError(err),6176 .OPERATION_ABORTED => {
6177 try syscall.checkCancel();
6178 continue;
6179 },
6180 else => |err| {
6181 syscall.finish();
6182 return windows.unexpectedError(err);
6183 },
6184 }
6011 }6185 }
6012}6186}
60136187
...@@ -6074,9 +6248,22 @@ fn fileIsTty(userdata: ?*anyopaque, file: File) Io.Cancelable!bool {...@@ -6074,9 +6248,22 @@ fn fileIsTty(userdata: ?*anyopaque, file: File) Io.Cancelable!bool {
6074fn isTty(file: File) Io.Cancelable!bool {6248fn isTty(file: File) Io.Cancelable!bool {
6075 if (is_windows) {6249 if (is_windows) {
6076 if (try isCygwinPty(file)) return true;6250 if (try isCygwinPty(file)) return true;
6077 try Thread.checkCancel();
6078 var out: windows.DWORD = undefined;6251 var out: windows.DWORD = undefined;
6079 return windows.kernel32.GetConsoleMode(file.handle, &out) != 0;6252 const syscall: Syscall = try .start();
6253 while (windows.kernel32.GetConsoleMode(file.handle, &out) == 0) {
6254 switch (windows.GetLastError()) {
6255 .OPERATION_ABORTED => {
6256 try syscall.checkCancel();
6257 continue;
6258 },
6259 else => {
6260 syscall.finish();
6261 return false;
6262 },
6263 }
6264 }
6265 syscall.finish();
6266 return true;
6080 }6267 }
60816268
6082 if (builtin.link_libc) {6269 if (builtin.link_libc) {
...@@ -6146,35 +6333,65 @@ fn fileEnableAnsiEscapeCodes(userdata: ?*anyopaque, file: File) File.EnableAnsiE...@@ -6146,35 +6333,65 @@ fn fileEnableAnsiEscapeCodes(userdata: ?*anyopaque, file: File) File.EnableAnsiE
6146 const t: *Threaded = @ptrCast(@alignCast(userdata));6333 const t: *Threaded = @ptrCast(@alignCast(userdata));
6147 _ = t;6334 _ = t;
61486335
6149 if (is_windows) {6336 if (!is_windows) {
6150 try Thread.checkCancel();6337 if (try supportsAnsiEscapeCodes(file)) return;
6338 return error.NotTerminalDevice;
6339 }
61516340
6152 // For Windows Terminal, VT Sequences processing is enabled by default.6341 // For Windows Terminal, VT Sequences processing is enabled by default.
6153 var original_console_mode: windows.DWORD = 0;6342 var original_console_mode: windows.DWORD = 0;
6154 if (windows.kernel32.GetConsoleMode(file.handle, &original_console_mode) != 0) {
6155 if (original_console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return;
61566343
6157 // For Windows Console, VT Sequences processing support was added in Windows 10 build 14361, but disabled by default.6344 {
6158 // https://devblogs.microsoft.com/commandline/tmux-support-arrives-for-bash-on-ubuntu-on-windows/6345 const syscall: Syscall = try .start();
6159 //6346 while (windows.kernel32.GetConsoleMode(file.handle, &original_console_mode) == 0) {
6160 // Note: In Microsoft's example for enabling virtual terminal processing, it6347 switch (windows.GetLastError()) {
6161 // shows attempting to enable `DISABLE_NEWLINE_AUTO_RETURN` as well:6348 .OPERATION_ABORTED => {
6162 // https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing6349 try syscall.checkCancel();
6163 // This is avoided because in the old Windows Console, that flag causes \n (as opposed to \r\n)6350 continue;
6164 // to behave unexpectedly (the cursor moves down 1 row but remains on the same column).6351 },
6165 // Additionally, the default console mode in Windows Terminal does not have6352 else => {
6166 // `DISABLE_NEWLINE_AUTO_RETURN` set, so by only enabling `ENABLE_VIRTUAL_TERMINAL_PROCESSING`6353 syscall.finish();
6167 // we end up matching the mode of Windows Terminal.6354 if (try isCygwinPty(file)) return;
6168 const requested_console_modes = windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING;6355 return error.NotTerminalDevice;
6169 const console_mode = original_console_mode | requested_console_modes;6356 },
6170 try Thread.checkCancel();6357 }
6171 if (windows.kernel32.SetConsoleMode(file.handle, console_mode) != 0) return;
6172 }6358 }
6173 if (try isCygwinPty(file)) return;6359 syscall.finish();
6174 } else {6360 }
6175 if (try supportsAnsiEscapeCodes(file)) return;6361
6362 if (original_console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return;
6363
6364 // For Windows Console, VT Sequences processing support was added in Windows 10 build 14361, but disabled by default.
6365 // https://devblogs.microsoft.com/commandline/tmux-support-arrives-for-bash-on-ubuntu-on-windows/
6366 //
6367 // Note: In Microsoft's example for enabling virtual terminal processing, it
6368 // shows attempting to enable `DISABLE_NEWLINE_AUTO_RETURN` as well:
6369 // https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing
6370 // This is avoided because in the old Windows Console, that flag causes \n (as opposed to \r\n)
6371 // to behave unexpectedly (the cursor moves down 1 row but remains on the same column).
6372 // Additionally, the default console mode in Windows Terminal does not have
6373 // `DISABLE_NEWLINE_AUTO_RETURN` set, so by only enabling `ENABLE_VIRTUAL_TERMINAL_PROCESSING`
6374 // we end up matching the mode of Windows Terminal.
6375 const requested_console_modes = windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING;
6376 const console_mode = original_console_mode | requested_console_modes;
6377
6378 {
6379 const syscall: Syscall = try .start();
6380 while (windows.kernel32.SetConsoleMode(file.handle, console_mode) == 0) {
6381 switch (windows.GetLastError()) {
6382 .OPERATION_ABORTED => {
6383 try syscall.checkCancel();
6384 continue;
6385 },
6386 else => {
6387 syscall.finish();
6388 if (try isCygwinPty(file)) return;
6389 return error.NotTerminalDevice;
6390 },
6391 }
6392 }
6393 syscall.finish();
6176 }6394 }
6177 return error.NotTerminalDevice;
6178}6395}
61796396
6180fn fileSupportsAnsiEscapeCodes(userdata: ?*anyopaque, file: File) Io.Cancelable!bool {6397fn fileSupportsAnsiEscapeCodes(userdata: ?*anyopaque, file: File) Io.Cancelable!bool {
...@@ -6185,11 +6402,27 @@ fn fileSupportsAnsiEscapeCodes(userdata: ?*anyopaque, file: File) Io.Cancelable!...@@ -6185,11 +6402,27 @@ fn fileSupportsAnsiEscapeCodes(userdata: ?*anyopaque, file: File) Io.Cancelable!
61856402
6186fn supportsAnsiEscapeCodes(file: File) Io.Cancelable!bool {6403fn supportsAnsiEscapeCodes(file: File) Io.Cancelable!bool {
6187 if (is_windows) {6404 if (is_windows) {
6188 try Thread.checkCancel();
6189 var console_mode: windows.DWORD = 0;6405 var console_mode: windows.DWORD = 0;
6190 if (windows.kernel32.GetConsoleMode(file.handle, &console_mode) != 0) {6406
6191 if (console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return true;6407 const syscall: Syscall = try .start();
6408 while (windows.kernel32.GetConsoleMode(file.handle, &console_mode) == 0) {
6409 switch (windows.GetLastError()) {
6410 .OPERATION_ABORTED => {
6411 try syscall.checkCancel();
6412 continue;
6413 },
6414 else => {
6415 syscall.finish();
6416 break;
6417 },
6418 }
6419 } else {
6420 syscall.finish();
6421 if (console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) {
6422 return true;
6423 }
6192 }6424 }
6425
6193 return isCygwinPty(file);6426 return isCygwinPty(file);
6194 }6427 }
61956428
...@@ -6220,20 +6453,26 @@ fn isCygwinPty(file: File) Io.Cancelable!bool {...@@ -6220,20 +6453,26 @@ fn isCygwinPty(file: File) Io.Cancelable!bool {
6220 // This allows us to avoid the more costly NtQueryInformationFile call6453 // This allows us to avoid the more costly NtQueryInformationFile call
6221 // for handles that aren't named pipes.6454 // for handles that aren't named pipes.
6222 {6455 {
6223 try Thread.checkCancel();
6224 var io_status: windows.IO_STATUS_BLOCK = undefined;6456 var io_status: windows.IO_STATUS_BLOCK = undefined;
6225 var device_info: windows.FILE.FS_DEVICE_INFORMATION = undefined;6457 var device_info: windows.FILE.FS_DEVICE_INFORMATION = undefined;
6226 const rc = windows.ntdll.NtQueryVolumeInformationFile(6458 const syscall: Syscall = try .start();
6459 while (true) switch (windows.ntdll.NtQueryVolumeInformationFile(
6227 handle,6460 handle,
6228 &io_status,6461 &io_status,
6229 &device_info,6462 &device_info,
6230 @sizeOf(windows.FILE.FS_DEVICE_INFORMATION),6463 @sizeOf(windows.FILE.FS_DEVICE_INFORMATION),
6231 .Device,6464 .Device,
6232 );6465 )) {
6233 switch (rc) {6466 .SUCCESS => break syscall.finish(),
6234 .SUCCESS => {},6467 .CANCELLED => {
6235 else => return false,6468 try syscall.checkCancel();
6236 }6469 continue;
6470 },
6471 else => {
6472 syscall.finish();
6473 return false;
6474 },
6475 };
6237 if (device_info.DeviceType.FileDevice != .NAMED_PIPE) return false;6476 if (device_info.DeviceType.FileDevice != .NAMED_PIPE) return false;
6238 }6477 }
62396478
...@@ -6248,19 +6487,25 @@ fn isCygwinPty(file: File) Io.Cancelable!bool {...@@ -6248,19 +6487,25 @@ fn isCygwinPty(file: File) Io.Cancelable!bool {
6248 var name_info_bytes align(@alignOf(windows.FILE.NAME_INFORMATION)) = [_]u8{0} ** (name_bytes_offset + num_name_bytes);6487 var name_info_bytes align(@alignOf(windows.FILE.NAME_INFORMATION)) = [_]u8{0} ** (name_bytes_offset + num_name_bytes);
62496488
6250 var io_status_block: windows.IO_STATUS_BLOCK = undefined;6489 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
6251 try Thread.checkCancel();6490 const syscall: Syscall = try .start();
6252 const rc = windows.ntdll.NtQueryInformationFile(6491 while (true) switch (windows.ntdll.NtQueryInformationFile(
6253 handle,6492 handle,
6254 &io_status_block,6493 &io_status_block,
6255 &name_info_bytes,6494 &name_info_bytes,
6256 @intCast(name_info_bytes.len),6495 @intCast(name_info_bytes.len),
6257 .Name,6496 .Name,
6258 );6497 )) {
6259 switch (rc) {6498 .SUCCESS => break syscall.finish(),
6260 .SUCCESS => {},6499 .CANCELLED => {
6500 try syscall.checkCancel();
6501 continue;
6502 },
6261 .INVALID_PARAMETER => unreachable,6503 .INVALID_PARAMETER => unreachable,
6262 else => return false,6504 else => {
6263 }6505 syscall.finish();
6506 return false;
6507 },
6508 };
62646509
6265 const name_info: *const windows.FILE_NAME_INFO = @ptrCast(&name_info_bytes);6510 const name_info: *const windows.FILE_NAME_INFO = @ptrCast(&name_info_bytes);
6266 const name_bytes = name_info_bytes[name_bytes_offset .. name_bytes_offset + name_info.FileNameLength];6511 const name_bytes = name_info_bytes[name_bytes_offset .. name_bytes_offset + name_info.FileNameLength];
...@@ -6279,28 +6524,30 @@ fn fileSetLength(userdata: ?*anyopaque, file: File, length: u64) File.SetLengthE...@@ -6279,28 +6524,30 @@ fn fileSetLength(userdata: ?*anyopaque, file: File, length: u64) File.SetLengthE
6279 if (signed_len < 0) return error.FileTooBig; // Avoid ambiguous EINVAL errors.6524 if (signed_len < 0) return error.FileTooBig; // Avoid ambiguous EINVAL errors.
62806525
6281 if (is_windows) {6526 if (is_windows) {
6282 try Thread.checkCancel();
6283
6284 var io_status_block: windows.IO_STATUS_BLOCK = undefined;6527 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
6285 const eof_info: windows.FILE.END_OF_FILE_INFORMATION = .{6528 const eof_info: windows.FILE.END_OF_FILE_INFORMATION = .{
6286 .EndOfFile = signed_len,6529 .EndOfFile = signed_len,
6287 };6530 };
62886531
6289 const status = windows.ntdll.NtSetInformationFile(6532 const syscall: Syscall = try .start();
6533 while (true) switch (windows.ntdll.NtSetInformationFile(
6290 file.handle,6534 file.handle,
6291 &io_status_block,6535 &io_status_block,
6292 &eof_info,6536 &eof_info,
6293 @sizeOf(windows.FILE.END_OF_FILE_INFORMATION),6537 @sizeOf(windows.FILE.END_OF_FILE_INFORMATION),
6294 .EndOfFile,6538 .EndOfFile,
6295 );6539 )) {
6296 switch (status) {6540 .SUCCESS => return syscall.finish(),
6297 .SUCCESS => return,6541 .CANCELLED => {
6298 .INVALID_HANDLE => |err| return windows.statusBug(err), // Handle not open for writing.6542 try syscall.checkCancel();
6299 .ACCESS_DENIED => return error.AccessDenied,6543 continue;
6300 .USER_MAPPED_FILE => return error.AccessDenied,6544 },
6301 .INVALID_PARAMETER => return error.FileTooBig,6545 .INVALID_HANDLE => |err| return syscall.ntstatusBug(err), // Handle not open for writing.
6302 else => return windows.unexpectedStatus(status),6546 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
6303 }6547 .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied),
6548 .INVALID_PARAMETER => return syscall.fail(error.FileTooBig),
6549 else => |status| return syscall.unexpectedNtstatus(status),
6550 };
6304 }6551 }
63056552
6306 if (native_os == .wasi and !builtin.link_libc) {6553 if (native_os == .wasi and !builtin.link_libc) {
...@@ -6368,7 +6615,6 @@ fn fileSetPermissions(userdata: ?*anyopaque, file: File, permissions: File.Permi...@@ -6368,7 +6615,6 @@ fn fileSetPermissions(userdata: ?*anyopaque, file: File, permissions: File.Permi
6368 _ = t;6615 _ = t;
6369 switch (native_os) {6616 switch (native_os) {
6370 .windows => {6617 .windows => {
6371 try Thread.checkCancel();
6372 var io_status_block: windows.IO_STATUS_BLOCK = undefined;6618 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
6373 const info: windows.FILE.BASIC_INFORMATION = .{6619 const info: windows.FILE.BASIC_INFORMATION = .{
6374 .CreationTime = 0,6620 .CreationTime = 0,
...@@ -6377,19 +6623,23 @@ fn fileSetPermissions(userdata: ?*anyopaque, file: File, permissions: File.Permi...@@ -6377,19 +6623,23 @@ fn fileSetPermissions(userdata: ?*anyopaque, file: File, permissions: File.Permi
6377 .ChangeTime = 0,6623 .ChangeTime = 0,
6378 .FileAttributes = permissions.toAttributes(),6624 .FileAttributes = permissions.toAttributes(),
6379 };6625 };
6380 const status = windows.ntdll.NtSetInformationFile(6626 const syscall: Syscall = try .start();
6627 while (true) switch (windows.ntdll.NtSetInformationFile(
6381 file.handle,6628 file.handle,
6382 &io_status_block,6629 &io_status_block,
6383 &info,6630 &info,
6384 @sizeOf(windows.FILE.BASIC_INFORMATION),6631 @sizeOf(windows.FILE.BASIC_INFORMATION),
6385 .Basic,6632 .Basic,
6386 );6633 )) {
6387 switch (status) {6634 .SUCCESS => return syscall.finish(),
6388 .SUCCESS => return,6635 .CANCELLED => {
6389 .INVALID_HANDLE => |err| return windows.statusBug(err),6636 try syscall.checkCancel();
6390 .ACCESS_DENIED => return error.AccessDenied,6637 continue;
6391 else => return windows.unexpectedStatus(status),6638 },
6392 }6639 .INVALID_HANDLE => |err| return syscall.ntstatusBug(err),
6640 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
6641 else => |status| return syscall.unexpectedNtstatus(status),
6642 };
6393 },6643 },
6394 .wasi => return error.Unexpected, // Unsupported OS.6644 .wasi => return error.Unexpected, // Unsupported OS.
6395 else => return setPermissionsPosix(file.handle, permissions.toMode()),6645 else => return setPermissionsPosix(file.handle, permissions.toMode()),
...@@ -6484,8 +6734,6 @@ fn fileSetTimestamps(...@@ -6484,8 +6734,6 @@ fn fileSetTimestamps(
6484 _ = t;6734 _ = t;
64856735
6486 if (is_windows) {6736 if (is_windows) {
6487 try Thread.checkCancel();
6488
6489 var access_time_buffer: windows.FILETIME = undefined;6737 var access_time_buffer: windows.FILETIME = undefined;
6490 var modify_time_buffer: windows.FILETIME = undefined;6738 var modify_time_buffer: windows.FILETIME = undefined;
6491 var system_time_buffer: windows.LARGE_INTEGER = undefined;6739 var system_time_buffer: windows.LARGE_INTEGER = undefined;
...@@ -6513,13 +6761,22 @@ fn fileSetTimestamps(...@@ -6513,13 +6761,22 @@ fn fileSetTimestamps(
6513 };6761 };
65146762
6515 // https://github.com/ziglang/zig/issues/18406763 // https://github.com/ziglang/zig/issues/1840
6516 const rc = windows.kernel32.SetFileTime(file.handle, null, access_ptr, modify_ptr);6764 const syscall: Syscall = try .start();
6517 if (rc == 0) {6765 while (true) {
6518 switch (windows.GetLastError()) {6766 switch (windows.kernel32.SetFileTime(file.handle, null, access_ptr, modify_ptr)) {
6519 else => |err| return windows.unexpectedError(err),6767 0 => switch (windows.GetLastError()) {
6768 .OPERATION_ABORTED => {
6769 try syscall.checkCancel();
6770 continue;
6771 },
6772 else => |err| {
6773 syscall.finish();
6774 return windows.unexpectedError(err);
6775 },
6776 },
6777 else => return syscall.finish(),
6520 }6778 }
6521 }6779 }
6522 return;
6523 }6780 }
65246781
6525 if (native_os == .wasi and !builtin.link_libc) {6782 if (native_os == .wasi and !builtin.link_libc) {
...@@ -6601,27 +6858,26 @@ fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!v...@@ -6601,27 +6858,26 @@ fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!v
6601 .none => {6858 .none => {
6602 // To match the non-Windows behavior, unlock6859 // To match the non-Windows behavior, unlock
6603 var io_status_block: windows.IO_STATUS_BLOCK = undefined;6860 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
6604 const status = windows.ntdll.NtUnlockFile(6861 while (true) switch (windows.ntdll.NtUnlockFile(
6605 file.handle,6862 file.handle,
6606 &io_status_block,6863 &io_status_block,
6607 &windows_lock_range_off,6864 &windows_lock_range_off,
6608 &windows_lock_range_len,6865 &windows_lock_range_len,
6609 0,6866 0,
6610 );6867 )) {
6611 switch (status) {6868 .SUCCESS => return,
6612 .SUCCESS => {},6869 .CANCELLED => continue,
6613 .RANGE_NOT_LOCKED => {},6870 .RANGE_NOT_LOCKED => return,
6614 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer6871 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
6615 else => return windows.unexpectedStatus(status),6872 else => |status| return windows.unexpectedStatus(status),
6616 }6873 };
6617 return;
6618 },6874 },
6619 .shared => false,6875 .shared => false,
6620 .exclusive => true,6876 .exclusive => true,
6621 };6877 };
6622 try Thread.checkCancel();
6623 var io_status_block: windows.IO_STATUS_BLOCK = undefined;6878 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
6624 const status = windows.ntdll.NtLockFile(6879 const syscall: Syscall = try .start();
6880 while (true) switch (windows.ntdll.NtLockFile(
6625 file.handle,6881 file.handle,
6626 null,6882 null,
6627 null,6883 null,
...@@ -6632,14 +6888,17 @@ fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!v...@@ -6632,14 +6888,17 @@ fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!v
6632 null,6888 null,
6633 windows.FALSE,6889 windows.FALSE,
6634 @intFromBool(exclusive),6890 @intFromBool(exclusive),
6635 );6891 )) {
6636 switch (status) {6892 .SUCCESS => return syscall.finish(),
6637 .SUCCESS => return,6893 .CANCELLED => {
6638 .INSUFFICIENT_RESOURCES => return error.SystemResources,6894 try syscall.checkCancel();
6639 .LOCK_NOT_GRANTED => |err| return windows.statusBug(err), // passed FailImmediately=false6895 continue;
6640 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer6896 },
6641 else => return windows.unexpectedStatus(status),6897 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
6642 }6898 .LOCK_NOT_GRANTED => |err| return syscall.ntstatusBug(err), // passed FailImmediately=false
6899 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer
6900 else => |status| return syscall.unexpectedNtstatus(status),
6901 };
6643 }6902 }
66446903
6645 const operation: i32 = switch (lock) {6904 const operation: i32 = switch (lock) {
...@@ -6680,26 +6939,26 @@ fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockErro...@@ -6680,26 +6939,26 @@ fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockErro
6680 .none => {6939 .none => {
6681 // To match the non-Windows behavior, unlock6940 // To match the non-Windows behavior, unlock
6682 var io_status_block: windows.IO_STATUS_BLOCK = undefined;6941 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
6683 const status = windows.ntdll.NtUnlockFile(6942 while (true) switch (windows.ntdll.NtUnlockFile(
6684 file.handle,6943 file.handle,
6685 &io_status_block,6944 &io_status_block,
6686 &windows_lock_range_off,6945 &windows_lock_range_off,
6687 &windows_lock_range_len,6946 &windows_lock_range_len,
6688 0,6947 0,
6689 );6948 )) {
6690 switch (status) {
6691 .SUCCESS => return true,6949 .SUCCESS => return true,
6950 .CANCELLED => continue,
6692 .RANGE_NOT_LOCKED => return false,6951 .RANGE_NOT_LOCKED => return false,
6693 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer6952 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
6694 else => return windows.unexpectedStatus(status),6953 else => |status| return windows.unexpectedStatus(status),
6695 }6954 };
6696 },6955 },
6697 .shared => false,6956 .shared => false,
6698 .exclusive => true,6957 .exclusive => true,
6699 };6958 };
6700 try Thread.checkCancel();
6701 var io_status_block: windows.IO_STATUS_BLOCK = undefined;6959 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
6702 const status = windows.ntdll.NtLockFile(6960 const syscall: Syscall = try .start();
6961 while (true) switch (windows.ntdll.NtLockFile(
6703 file.handle,6962 file.handle,
6704 null,6963 null,
6705 null,6964 null,
...@@ -6710,14 +6969,23 @@ fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockErro...@@ -6710,14 +6969,23 @@ fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockErro
6710 null,6969 null,
6711 windows.TRUE,6970 windows.TRUE,
6712 @intFromBool(exclusive),6971 @intFromBool(exclusive),
6713 );6972 )) {
6714 switch (status) {6973 .SUCCESS => {
6715 .SUCCESS => return true,6974 syscall.finish();
6716 .INSUFFICIENT_RESOURCES => return error.SystemResources,6975 return true;
6717 .LOCK_NOT_GRANTED => return false,6976 },
6718 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer6977 .LOCK_NOT_GRANTED => {
6719 else => return windows.unexpectedStatus(status),6978 syscall.finish();
6720 }6979 return false;
6980 },
6981 .CANCELLED => {
6982 try syscall.checkCancel();
6983 continue;
6984 },
6985 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
6986 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer
6987 else => |status| return syscall.unexpectedNtstatus(status),
6988 };
6721 }6989 }
67226990
6723 const operation: i32 = switch (lock) {6991 const operation: i32 = switch (lock) {
...@@ -6761,20 +7029,19 @@ fn fileUnlock(userdata: ?*anyopaque, file: File) void {...@@ -6761,20 +7029,19 @@ fn fileUnlock(userdata: ?*anyopaque, file: File) void {
67617029
6762 if (is_windows) {7030 if (is_windows) {
6763 var io_status_block: windows.IO_STATUS_BLOCK = undefined;7031 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
6764 const status = windows.ntdll.NtUnlockFile(7032 while (true) switch (windows.ntdll.NtUnlockFile(
6765 file.handle,7033 file.handle,
6766 &io_status_block,7034 &io_status_block,
6767 &windows_lock_range_off,7035 &windows_lock_range_off,
6768 &windows_lock_range_len,7036 &windows_lock_range_len,
6769 0,7037 0,
6770 );7038 )) {
6771 if (is_debug) switch (status) {7039 .SUCCESS => return,
6772 .SUCCESS => {},7040 .CANCELLED => continue,
6773 .RANGE_NOT_LOCKED => unreachable, // Function asserts unlocked.7041 .RANGE_NOT_LOCKED => if (is_debug) unreachable else return, // Function asserts unlocked.
6774 .ACCESS_VIOLATION => unreachable, // bad io_status_block pointer7042 .ACCESS_VIOLATION => if (is_debug) unreachable else return, // bad io_status_block pointer
6775 else => unreachable, // Resource deallocation must succeed.7043 else => if (is_debug) unreachable else return, // Resource deallocation must succeed.
6776 };7044 };
6777 return;
6778 }7045 }
67797046
6780 while (true) {7047 while (true) {
...@@ -6797,14 +7064,14 @@ fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError!...@@ -6797,14 +7064,14 @@ fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError!
6797 _ = t;7064 _ = t;
67987065
6799 if (is_windows) {7066 if (is_windows) {
6800 try Thread.checkCancel();
6801 // On Windows it works like a semaphore + exclusivity flag. To7067 // On Windows it works like a semaphore + exclusivity flag. To
6802 // implement this function, we first obtain another lock in shared7068 // implement this function, we first obtain another lock in shared
6803 // mode. This changes the exclusivity flag, but increments the7069 // mode. This changes the exclusivity flag, but increments the
6804 // semaphore to 2. So we follow up with an NtUnlockFile which7070 // semaphore to 2. So we follow up with an NtUnlockFile which
6805 // decrements the semaphore but does not modify the exclusivity flag.7071 // decrements the semaphore but does not modify the exclusivity flag.
6806 var io_status_block: windows.IO_STATUS_BLOCK = undefined;7072 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
6807 switch (windows.ntdll.NtLockFile(7073 const syscall: Syscall = try .start();
7074 while (true) switch (windows.ntdll.NtLockFile(
6808 file.handle,7075 file.handle,
6809 null,7076 null,
6810 null,7077 null,
...@@ -6816,26 +7083,29 @@ fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError!...@@ -6816,26 +7083,29 @@ fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError!
6816 windows.TRUE,7083 windows.TRUE,
6817 windows.FALSE,7084 windows.FALSE,
6818 )) {7085 )) {
6819 .SUCCESS => {},7086 .SUCCESS => break syscall.finish(),
6820 .INSUFFICIENT_RESOURCES => |err| return windows.statusBug(err),7087 .CANCELLED => {
6821 .LOCK_NOT_GRANTED => |err| return windows.statusBug(err), // File was not locked in exclusive mode.7088 try syscall.checkCancel();
6822 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer7089 continue;
6823 else => |status| return windows.unexpectedStatus(status),7090 },
6824 }7091 .INSUFFICIENT_RESOURCES => |err| return syscall.ntstatusBug(err),
6825 const status = windows.ntdll.NtUnlockFile(7092 .LOCK_NOT_GRANTED => |err| return syscall.ntstatusBug(err), // File was not locked in exclusive mode.
7093 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer
7094 else => |status| return syscall.unexpectedNtstatus(status),
7095 };
7096 while (true) switch (windows.ntdll.NtUnlockFile(
6826 file.handle,7097 file.handle,
6827 &io_status_block,7098 &io_status_block,
6828 &windows_lock_range_off,7099 &windows_lock_range_off,
6829 &windows_lock_range_len,7100 &windows_lock_range_len,
6830 0,7101 0,
6831 );7102 )) {
6832 if (is_debug) switch (status) {7103 .SUCCESS => return,
6833 .SUCCESS => {},7104 .CANCELLED => continue,
6834 .RANGE_NOT_LOCKED => unreachable, // File was not locked.7105 .RANGE_NOT_LOCKED => if (is_debug) unreachable else return, // File was not locked.
6835 .ACCESS_VIOLATION => unreachable, // bad io_status_block pointer7106 .ACCESS_VIOLATION => if (is_debug) unreachable else return, // bad io_status_block pointer
6836 else => unreachable, // Resource deallocation must succeed.7107 else => if (is_debug) unreachable else return, // Resource deallocation must succeed.
6837 };7108 };
6838 return;
6839 }7109 }
68407110
6841 const operation = posix.LOCK.SH | posix.LOCK.NB;7111 const operation = posix.LOCK.SH | posix.LOCK.NB;
...@@ -7158,21 +7428,34 @@ fn fileReadStreamingWindows(userdata: ?*anyopaque, file: File, data: []const []u...@@ -7158,21 +7428,34 @@ fn fileReadStreamingWindows(userdata: ?*anyopaque, file: File, data: []const []u
7158 const buffer = data[index];7428 const buffer = data[index];
7159 const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len);7429 const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len);
71607430
7431 const syscall: Syscall = try .start();
7161 while (true) {7432 while (true) {
7162 try Thread.checkCancel();
7163 var n: DWORD = undefined;7433 var n: DWORD = undefined;
7164 if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) != 0)7434 if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) != 0) {
7435 syscall.finish();
7165 return n;7436 return n;
7437 }
7166 switch (windows.GetLastError()) {7438 switch (windows.GetLastError()) {
7167 .IO_PENDING => |err| return windows.errorBug(err),7439 .IO_PENDING => |err| {
7168 .OPERATION_ABORTED => continue,7440 syscall.finish();
7169 .BROKEN_PIPE => return 0,7441 return windows.errorBug(err);
7170 .HANDLE_EOF => return 0,7442 },
7171 .NETNAME_DELETED => return error.ConnectionResetByPeer,7443 .OPERATION_ABORTED => {
7172 .LOCK_VIOLATION => return error.LockViolation,7444 try syscall.checkCancel();
7173 .ACCESS_DENIED => return error.AccessDenied,7445 continue;
7174 .INVALID_HANDLE => return error.NotOpenForReading,7446 },
7175 else => |err| return windows.unexpectedError(err),7447 .BROKEN_PIPE, .HANDLE_EOF => {
7448 syscall.finish();
7449 return 0;
7450 },
7451 .NETNAME_DELETED => return syscall.fail(error.ConnectionResetByPeer),
7452 .LOCK_VIOLATION => return syscall.fail(error.LockViolation),
7453 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
7454 .INVALID_HANDLE => return syscall.fail(error.NotOpenForReading),
7455 else => |err| {
7456 syscall.finish();
7457 return windows.unexpectedError(err);
7458 },
7176 }7459 }
7177 }7460 }
7178}7461}
...@@ -7302,21 +7585,34 @@ fn fileReadPositionalWindows(userdata: ?*anyopaque, file: File, data: []const []...@@ -7302,21 +7585,34 @@ fn fileReadPositionalWindows(userdata: ?*anyopaque, file: File, data: []const []
7302 .hEvent = null,7585 .hEvent = null,
7303 };7586 };
73047587
7588 const syscall: Syscall = try .start();
7305 while (true) {7589 while (true) {
7306 try Thread.checkCancel();
7307 var n: DWORD = undefined;7590 var n: DWORD = undefined;
7308 if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, &overlapped) != 0)7591 if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, &overlapped) != 0) {
7592 syscall.finish();
7309 return n;7593 return n;
7594 }
7310 switch (windows.GetLastError()) {7595 switch (windows.GetLastError()) {
7311 .IO_PENDING => |err| return windows.errorBug(err),7596 .IO_PENDING => |err| {
7312 .OPERATION_ABORTED => continue,7597 syscall.finish();
7313 .BROKEN_PIPE => return 0,7598 return windows.errorBug(err);
7314 .HANDLE_EOF => return 0,7599 },
7315 .NETNAME_DELETED => return error.ConnectionResetByPeer,7600 .OPERATION_ABORTED => {
7316 .LOCK_VIOLATION => return error.LockViolation,7601 try syscall.checkCancel();
7317 .ACCESS_DENIED => return error.AccessDenied,7602 continue;
7318 .INVALID_HANDLE => return error.NotOpenForReading,7603 },
7319 else => |err| return windows.unexpectedError(err),7604 .BROKEN_PIPE, .HANDLE_EOF => {
7605 syscall.finish();
7606 return 0;
7607 },
7608 .NETNAME_DELETED => return syscall.fail(error.ConnectionResetByPeer),
7609 .LOCK_VIOLATION => return syscall.fail(error.LockViolation),
7610 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
7611 .INVALID_HANDLE => return syscall.fail(error.NotOpenForReading),
7612 else => |err| {
7613 syscall.finish();
7614 return windows.unexpectedError(err);
7615 },
7320 }7616 }
7321 }7617 }
7322}7618}
...@@ -7355,8 +7651,26 @@ fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!voi...@@ -7355,8 +7651,26 @@ fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!voi
7355 }7651 }
73567652
7357 if (native_os == .windows) {7653 if (native_os == .windows) {
7358 try Thread.checkCancel();7654 const syscall: Syscall = try .start();
7359 return windows.SetFilePointerEx_CURRENT(fd, offset);7655 while (true) {
7656 if (windows.kernel32.SetFilePointerEx(fd, offset, null, windows.FILE_CURRENT) != 0) {
7657 return syscall.finish();
7658 }
7659 switch (windows.GetLastError()) {
7660 .OPERATION_ABORTED => {
7661 try syscall.checkCancel();
7662 continue;
7663 },
7664 .INVALID_FUNCTION => return syscall.fail(error.Unseekable),
7665 .NEGATIVE_SEEK => return syscall.fail(error.Unseekable),
7666 .INVALID_PARAMETER => unreachable,
7667 .INVALID_HANDLE => unreachable,
7668 else => |err| {
7669 syscall.finish();
7670 return windows.unexpectedError(err);
7671 },
7672 }
7673 }
7360 }7674 }
73617675
7362 if (native_os == .wasi and !builtin.link_libc) {7676 if (native_os == .wasi and !builtin.link_libc) {
...@@ -7422,8 +7736,31 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!voi...@@ -7422,8 +7736,31 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!voi
7422 const fd = file.handle;7736 const fd = file.handle;
74237737
7424 if (native_os == .windows) {7738 if (native_os == .windows) {
7425 try Thread.checkCancel();7739 // "The starting point is zero or the beginning of the file. If [FILE_BEGIN]
7426 return windows.SetFilePointerEx_BEGIN(fd, offset);7740 // is specified, then the liDistanceToMove parameter is interpreted as an unsigned value."
7741 // https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-setfilepointerex
7742 const ipos: windows.LARGE_INTEGER = @bitCast(offset);
7743
7744 const syscall: Syscall = try .start();
7745 while (true) {
7746 if (windows.kernel32.SetFilePointerEx(fd, ipos, null, windows.FILE_BEGIN) != 0) {
7747 return syscall.finish();
7748 }
7749 switch (windows.GetLastError()) {
7750 .OPERATION_ABORTED => {
7751 try syscall.checkCancel();
7752 continue;
7753 },
7754 .INVALID_FUNCTION => return syscall.fail(error.Unseekable),
7755 .NEGATIVE_SEEK => return syscall.fail(error.Unseekable),
7756 .INVALID_PARAMETER => unreachable,
7757 .INVALID_HANDLE => unreachable,
7758 else => |err| {
7759 syscall.finish();
7760 return windows.unexpectedError(err);
7761 },
7762 }
7763 }
7427 }7764 }
74287765
7429 if (native_os == .wasi and !builtin.link_libc) {7766 if (native_os == .wasi and !builtin.link_libc) {
...@@ -7527,7 +7864,7 @@ fn processExecutableOpen(userdata: ?*anyopaque, flags: File.OpenFlags) std.proce...@@ -7527,7 +7864,7 @@ fn processExecutableOpen(userdata: ?*anyopaque, flags: File.OpenFlags) std.proce
7527 const image_path_unicode_string = &windows.peb().ProcessParameters.ImagePathName;7864 const image_path_unicode_string = &windows.peb().ProcessParameters.ImagePathName;
7528 const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0];7865 const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0];
7529 const prefixed_path_w = try windows.wToPrefixedFileW(null, image_path_name);7866 const prefixed_path_w = try windows.wToPrefixedFileW(null, image_path_name);
7530 return dirOpenFileWtf16(t, null, prefixed_path_w.span(), flags);7867 return dirOpenFileWtf16(null, prefixed_path_w.span(), flags);
7531 },7868 },
7532 .driverkit,7869 .driverkit,
7533 .ios,7870 .ios,
...@@ -7736,7 +8073,6 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex...@@ -7736,7 +8073,6 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex
7736 return error.FileNotFound;8073 return error.FileNotFound;
7737 },8074 },
7738 .windows => {8075 .windows => {
7739 try Thread.checkCancel();
7740 const w = windows;8076 const w = windows;
7741 const image_path_unicode_string = &w.peb().ProcessParameters.ImagePathName;8077 const image_path_unicode_string = &w.peb().ProcessParameters.ImagePathName;
7742 const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0];8078 const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0];
...@@ -7746,24 +8082,34 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex...@@ -7746,24 +8082,34 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex
7746 // that the symlink points to, though, so we need to get the realpath.8082 // that the symlink points to, though, so we need to get the realpath.
7747 var path_name_w_buf = try w.wToPrefixedFileW(null, image_path_name);8083 var path_name_w_buf = try w.wToPrefixedFileW(null, image_path_name);
77488084
7749 const h_file = blk: {8085 const h_file = handle: {
7750 const res = w.OpenFile(path_name_w_buf.span(), .{8086 const syscall: Syscall = try .start();
7751 .dir = null,8087 while (true) {
7752 .access_mask = .{8088 if (w.OpenFile(path_name_w_buf.span(), .{
7753 .GENERIC = .{ .READ = true },8089 .dir = null,
7754 .STANDARD = .{ .SYNCHRONIZE = true },8090 .access_mask = .{
7755 },8091 .GENERIC = .{ .READ = true },
7756 .creation = .OPEN,8092 .STANDARD = .{ .SYNCHRONIZE = true },
7757 .filter = .any,8093 },
7758 }) catch |err| switch (err) {8094 .creation = .OPEN,
7759 error.WouldBlock => unreachable,8095 .filter = .any,
7760 else => |e| return e,8096 })) |handle| {
7761 };8097 syscall.finish();
7762 break :blk res;8098 break :handle handle;
8099 } else |err| switch (err) {
8100 error.WouldBlock => unreachable,
8101 error.OperationCanceled => {
8102 try syscall.checkCancel();
8103 continue;
8104 },
8105 else => |e| return e,
8106 }
8107 }
7763 };8108 };
7764 defer w.CloseHandle(h_file);8109 defer w.CloseHandle(h_file);
77658110
7766 // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks8111 // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks
8112 try Thread.checkCancel();
7767 const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, &path_name_w_buf.data);8113 const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, &path_name_w_buf.data);
77688114
7769 const len = std.unicode.calcWtf8Len(wide_slice);8115 const len = std.unicode.calcWtf8Len(wide_slice);
...@@ -7916,8 +8262,6 @@ fn writeFilePositionalWindows(...@@ -7916,8 +8262,6 @@ fn writeFilePositionalWindows(
7916 bytes: []const u8,8262 bytes: []const u8,
7917 offset: u64,8263 offset: u64,
7918) File.WritePositionalError!usize {8264) File.WritePositionalError!usize {
7919 try Thread.checkCancel();
7920
7921 var bytes_written: windows.DWORD = undefined;8265 var bytes_written: windows.DWORD = undefined;
7922 var overlapped: windows.OVERLAPPED = .{8266 var overlapped: windows.OVERLAPPED = .{
7923 .Internal = 0,8267 .Internal = 0,
...@@ -7931,21 +8275,31 @@ fn writeFilePositionalWindows(...@@ -7931,21 +8275,31 @@ fn writeFilePositionalWindows(
7931 .hEvent = null,8275 .hEvent = null,
7932 };8276 };
7933 const adjusted_len = std.math.lossyCast(u32, bytes.len);8277 const adjusted_len = std.math.lossyCast(u32, bytes.len);
7934 if (windows.kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, &overlapped) == 0) {8278 const syscall: Syscall = try .start();
8279 while (true) {
8280 if (windows.kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, &overlapped) != 0) {
8281 syscall.finish();
8282 return bytes_written;
8283 }
7935 switch (windows.GetLastError()) {8284 switch (windows.GetLastError()) {
7936 .INVALID_USER_BUFFER => return error.SystemResources,8285 .OPERATION_ABORTED => {
7937 .NOT_ENOUGH_MEMORY => return error.SystemResources,8286 try syscall.checkCancel();
7938 .OPERATION_ABORTED => return error.Canceled,8287 continue;
7939 .NOT_ENOUGH_QUOTA => return error.SystemResources,8288 },
7940 .NO_DATA => return error.BrokenPipe,8289 .INVALID_USER_BUFFER => return syscall.fail(error.SystemResources),
7941 .INVALID_HANDLE => return error.NotOpenForWriting,8290 .NOT_ENOUGH_MEMORY => return syscall.fail(error.SystemResources),
7942 .LOCK_VIOLATION => return error.LockViolation,8291 .NOT_ENOUGH_QUOTA => return syscall.fail(error.SystemResources),
7943 .ACCESS_DENIED => return error.AccessDenied,8292 .NO_DATA => return syscall.fail(error.BrokenPipe),
7944 .WORKING_SET_QUOTA => return error.SystemResources,8293 .INVALID_HANDLE => return syscall.fail(error.NotOpenForWriting),
7945 else => |err| return windows.unexpectedError(err),8294 .LOCK_VIOLATION => return syscall.fail(error.LockViolation),
8295 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
8296 .WORKING_SET_QUOTA => return syscall.fail(error.SystemResources),
8297 else => |err| {
8298 syscall.finish();
8299 return windows.unexpectedError(err);
8300 },
7946 }8301 }
7947 }8302 }
7948 return bytes_written;
7949}8303}
79508304
7951fn fileWriteStreaming(8305fn fileWriteStreaming(
...@@ -8078,25 +8432,33 @@ fn writeFileStreamingWindows(...@@ -8078,25 +8432,33 @@ fn writeFileStreamingWindows(
8078 handle: windows.HANDLE,8432 handle: windows.HANDLE,
8079 bytes: []const u8,8433 bytes: []const u8,
8080) File.Writer.Error!usize {8434) File.Writer.Error!usize {
8081 try Thread.checkCancel();
8082
8083 var bytes_written: windows.DWORD = undefined;8435 var bytes_written: windows.DWORD = undefined;
8084 const adjusted_len = std.math.lossyCast(u32, bytes.len);8436 const adjusted_len = std.math.lossyCast(u32, bytes.len);
8085 if (windows.kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, null) == 0) {8437 const syscall: Syscall = try .start();
8438 while (true) {
8439 if (windows.kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, null) != 0) {
8440 syscall.finish();
8441 return bytes_written;
8442 }
8086 switch (windows.GetLastError()) {8443 switch (windows.GetLastError()) {
8087 .INVALID_USER_BUFFER => return error.SystemResources,8444 .OPERATION_ABORTED => {
8088 .NOT_ENOUGH_MEMORY => return error.SystemResources,8445 try syscall.checkCancel();
8089 .OPERATION_ABORTED => return error.Canceled,8446 continue;
8090 .NOT_ENOUGH_QUOTA => return error.SystemResources,8447 },
8091 .NO_DATA => return error.BrokenPipe,8448 .INVALID_USER_BUFFER => return syscall.fail(error.SystemResources),
8092 .INVALID_HANDLE => return error.NotOpenForWriting,8449 .NOT_ENOUGH_MEMORY => return syscall.fail(error.SystemResources),
8093 .LOCK_VIOLATION => return error.LockViolation,8450 .NOT_ENOUGH_QUOTA => return syscall.fail(error.SystemResources),
8094 .ACCESS_DENIED => return error.AccessDenied,8451 .NO_DATA => return syscall.fail(error.BrokenPipe),
8095 .WORKING_SET_QUOTA => return error.SystemResources,8452 .INVALID_HANDLE => return syscall.fail(error.NotOpenForWriting),
8096 else => |err| return windows.unexpectedError(err),8453 .LOCK_VIOLATION => return syscall.fail(error.LockViolation),
8454 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
8455 .WORKING_SET_QUOTA => return syscall.fail(error.SystemResources),
8456 else => |err| {
8457 syscall.finish();
8458 return windows.unexpectedError(err);
8459 },
8097 }8460 }
8098 }8461 }
8099 return bytes_written;
8100}8462}
81018463
8102fn fileWriteFileStreaming(8464fn fileWriteFileStreaming(
...@@ -8716,9 +9078,7 @@ fn fileWriteFilePositional(...@@ -8716,9 +9078,7 @@ fn fileWriteFilePositional(
8716 return error.Unimplemented;9078 return error.Unimplemented;
8717}9079}
87189080
8719fn nowPosix(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {9081fn nowPosix(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
8720 const t: *Threaded = @ptrCast(@alignCast(userdata));
8721 _ = t;
8722 const clock_id: posix.clockid_t = clockToPosix(clock);9082 const clock_id: posix.clockid_t = clockToPosix(clock);
8723 var tp: posix.timespec = undefined;9083 var tp: posix.timespec = undefined;
8724 switch (posix.errno(posix.system.clock_gettime(clock_id, &tp))) {9084 switch (posix.errno(posix.system.clock_gettime(clock_id, &tp))) {
...@@ -8728,15 +9088,17 @@ fn nowPosix(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp...@@ -8728,15 +9088,17 @@ fn nowPosix(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp
8728 }9088 }
8729}9089}
87309090
8731const now = switch (native_os) {9091fn now(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
8732 .windows => nowWindows,
8733 .wasi => nowWasi,
8734 else => nowPosix,
8735};
8736
8737fn nowWindows(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
8738 const t: *Threaded = @ptrCast(@alignCast(userdata));9092 const t: *Threaded = @ptrCast(@alignCast(userdata));
8739 _ = t;9093 _ = t;
9094 return switch (native_os) {
9095 .windows => nowWindows(clock),
9096 .wasi => nowWasi(clock),
9097 else => nowPosix(clock),
9098 };
9099}
9100
9101fn nowWindows(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
8740 switch (clock) {9102 switch (clock) {
8741 .real => {9103 .real => {
8742 // RtlGetSystemTimePrecise() has a granularity of 100 nanoseconds9104 // RtlGetSystemTimePrecise() has a granularity of 100 nanoseconds
...@@ -8769,25 +9131,24 @@ fn nowWindows(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestam...@@ -8769,25 +9131,24 @@ fn nowWindows(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestam
8769 }9131 }
8770}9132}
87719133
8772fn nowWasi(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {9134fn nowWasi(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
8773 const t: *Threaded = @ptrCast(@alignCast(userdata));
8774 _ = t;
8775 var ns: std.os.wasi.timestamp_t = undefined;9135 var ns: std.os.wasi.timestamp_t = undefined;
8776 const err = std.os.wasi.clock_time_get(clockToWasi(clock), 1, &ns);9136 const err = std.os.wasi.clock_time_get(clockToWasi(clock), 1, &ns);
8777 if (err != .SUCCESS) return error.Unexpected;9137 if (err != .SUCCESS) return error.Unexpected;
8778 return .fromNanoseconds(ns);9138 return .fromNanoseconds(ns);
8779}9139}
87809140
8781const sleep = switch (native_os) {9141fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
8782 .windows => sleepWindows,
8783 .wasi => sleepWasi,
8784 .linux => sleepLinux,
8785 else => sleepPosix,
8786};
8787
8788fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
8789 const t: *Threaded = @ptrCast(@alignCast(userdata));9142 const t: *Threaded = @ptrCast(@alignCast(userdata));
8790 _ = t;9143 if (use_parking_sleep) return parking_sleep.sleep(timeout);
9144 switch (native_os) {
9145 .wasi => return sleepWasi(t, timeout),
9146 .linux => return sleepLinux(timeout),
9147 else => return sleepPosix(t, timeout),
9148 }
9149}
9150
9151fn sleepLinux(timeout: Io.Timeout) Io.SleepError!void {
8791 const clock_id: posix.clockid_t = clockToPosix(switch (timeout) {9152 const clock_id: posix.clockid_t = clockToPosix(switch (timeout) {
8792 .none => .awake,9153 .none => .awake,
8793 .duration => |d| d.clock,9154 .duration => |d| d.clock,
...@@ -8824,21 +9185,7 @@ fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {...@@ -8824,21 +9185,7 @@ fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
8824 }9185 }
8825}9186}
88269187
8827fn sleepWindows(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {9188fn sleepWasi(t: *Threaded, timeout: Io.Timeout) Io.SleepError!void {
8828 const t: *Threaded = @ptrCast(@alignCast(userdata));
8829 const t_io = ioBasic(t);
8830 try Thread.checkCancel();
8831 const ms = ms: {
8832 const d = (try timeout.toDurationFromNow(t_io)) orelse
8833 break :ms std.math.maxInt(windows.DWORD);
8834 break :ms std.math.lossyCast(windows.DWORD, d.raw.toMilliseconds());
8835 };
8836 // TODO: alertable true with checkCancel in a loop plus deadline
8837 _ = windows.kernel32.SleepEx(ms, windows.FALSE);
8838}
8839
8840fn sleepWasi(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
8841 const t: *Threaded = @ptrCast(@alignCast(userdata));
8842 const t_io = ioBasic(t);9189 const t_io = ioBasic(t);
8843 const w = std.os.wasi;9190 const w = std.os.wasi;
88449191
...@@ -8867,8 +9214,7 @@ fn sleepWasi(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {...@@ -8867,8 +9214,7 @@ fn sleepWasi(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
8867 syscall.finish();9214 syscall.finish();
8868}9215}
88699216
8870fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {9217fn sleepPosix(t: *Threaded, timeout: Io.Timeout) Io.SleepError!void {
8871 const t: *Threaded = @ptrCast(@alignCast(userdata));
8872 const t_io = ioBasic(t);9218 const t_io = ioBasic(t);
8873 const sec_type = @typeInfo(posix.timespec).@"struct".fields[0].type;9219 const sec_type = @typeInfo(posix.timespec).@"struct".fields[0].type;
8874 const nsec_type = @typeInfo(posix.timespec).@"struct".fields[1].type;9220 const nsec_type = @typeInfo(posix.timespec).@"struct".fields[1].type;
...@@ -9037,93 +9383,90 @@ fn netListenIpWindows(...@@ -9037,93 +9383,90 @@ fn netListenIpWindows(
9037 var storage: WsaAddress = undefined;9383 var storage: WsaAddress = undefined;
9038 var addr_len = addressToWsa(&address, &storage);9384 var addr_len = addressToWsa(&address, &storage);
90399385
9040 {9386 var syscall: Syscall = try .start();
9041 const syscall: Syscall = try .start();9387 while (true) {
9042 while (true) {9388 const rc = ws2_32.bind(socket_handle, &storage.any, addr_len);
9043 const rc = ws2_32.bind(socket_handle, &storage.any, addr_len);9389 if (rc != ws2_32.SOCKET_ERROR) {
9044 if (rc != ws2_32.SOCKET_ERROR) {9390 syscall.finish();
9045 syscall.finish();9391 break;
9046 break;
9047 }
9048 switch (ws2_32.WSAGetLastError()) {
9049 .EINTR => {
9050 try syscall.checkCancel();
9051 continue;
9052 },
9053 .NOTINITIALISED => {
9054 try initializeWsa(t);
9055 try syscall.checkCancel();
9056 continue;
9057 },
9058 else => |e| {
9059 syscall.finish();
9060 switch (e) {
9061 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
9062 .EADDRINUSE => return error.AddressInUse,
9063 .EADDRNOTAVAIL => return error.AddressUnavailable,
9064 .ENOTSOCK => |err| return wsaErrorBug(err),
9065 .EFAULT => |err| return wsaErrorBug(err),
9066 .EINVAL => |err| return wsaErrorBug(err),
9067 .ENOBUFS => return error.SystemResources,
9068 .ENETDOWN => return error.NetworkDown,
9069 else => |err| return windows.unexpectedWSAError(err),
9070 }
9071 },
9072 }
9073 }9392 }
9074 }9393 switch (ws2_32.WSAGetLastError()) {
9075 {9394 .NOTINITIALISED => {
9076 const syscall: Syscall = try .start();
9077 while (true) {
9078 const rc = ws2_32.listen(socket_handle, options.kernel_backlog);
9079 if (rc != ws2_32.SOCKET_ERROR) {
9080 syscall.finish();9395 syscall.finish();
9081 break;9396 try initializeWsa(t);
9082 }9397 syscall = try .start();
9083 switch (ws2_32.WSAGetLastError()) {9398 continue;
9084 .EINTR => {9399 },
9085 try syscall.checkCancel();9400 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
9086 continue;9401 try syscall.checkCancel();
9087 },9402 continue;
9088 .NOTINITIALISED => {9403 },
9089 try initializeWsa(t);9404 else => |e| {
9090 try syscall.checkCancel();9405 syscall.finish();
9091 continue;9406 switch (e) {
9092 },9407 .EADDRINUSE => return error.AddressInUse,
9093 else => |e| {9408 .EADDRNOTAVAIL => return error.AddressUnavailable,
9094 syscall.finish();9409 .ENOTSOCK => |err| return wsaErrorBug(err),
9095 switch (e) {9410 .EFAULT => |err| return wsaErrorBug(err),
9096 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,9411 .EINVAL => |err| return wsaErrorBug(err),
9097 .ENETDOWN => return error.NetworkDown,9412 .ENOBUFS => return error.SystemResources,
9098 .EADDRINUSE => return error.AddressInUse,9413 .ENETDOWN => return error.NetworkDown,
9099 .EISCONN => |err| return wsaErrorBug(err),9414 else => |err| return windows.unexpectedWSAError(err),
9100 .EINVAL => |err| return wsaErrorBug(err),9415 }
9101 .EMFILE, .ENOBUFS => return error.SystemResources,9416 },
9102 .ENOTSOCK => |err| return wsaErrorBug(err),
9103 .EOPNOTSUPP => |err| return wsaErrorBug(err),
9104 .EINPROGRESS => |err| return wsaErrorBug(err),
9105 else => |err| return windows.unexpectedWSAError(err),
9106 }
9107 },
9108 }
9109 }9417 }
9110 }9418 }
91119419
9112 try wsaGetSockName(t, socket_handle, &storage.any, &addr_len);9420 syscall = try .start();
91139421 while (true) {
9114 return .{9422 const rc = ws2_32.listen(socket_handle, options.kernel_backlog);
9115 .socket = .{9423 if (rc != ws2_32.SOCKET_ERROR) {
9116 .handle = socket_handle,9424 syscall.finish();
9117 .address = addressFromWsa(&storage),9425 break;
9118 },9426 }
9119 };9427 switch (ws2_32.WSAGetLastError()) {
9120}9428 .NOTINITIALISED => {
91219429 syscall.finish();
9122fn netListenIpUnavailable(9430 try initializeWsa(t);
9123 userdata: ?*anyopaque,9431 syscall = try .start();
9124 address: IpAddress,9432 continue;
9125 options: IpAddress.ListenOptions,9433 },
9126) IpAddress.ListenError!net.Server {9434 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
9435 try syscall.checkCancel();
9436 continue;
9437 },
9438 else => |e| {
9439 syscall.finish();
9440 switch (e) {
9441 .ENETDOWN => return error.NetworkDown,
9442 .EADDRINUSE => return error.AddressInUse,
9443 .EISCONN => |err| return wsaErrorBug(err),
9444 .EINVAL => |err| return wsaErrorBug(err),
9445 .EMFILE, .ENOBUFS => return error.SystemResources,
9446 .ENOTSOCK => |err| return wsaErrorBug(err),
9447 .EOPNOTSUPP => |err| return wsaErrorBug(err),
9448 .EINPROGRESS => |err| return wsaErrorBug(err),
9449 else => |err| return windows.unexpectedWSAError(err),
9450 }
9451 },
9452 }
9453 }
9454
9455 try wsaGetSockName(t, socket_handle, &storage.any, &addr_len);
9456
9457 return .{
9458 .socket = .{
9459 .handle = socket_handle,
9460 .address = addressFromWsa(&storage),
9461 },
9462 };
9463}
9464
9465fn netListenIpUnavailable(
9466 userdata: ?*anyopaque,
9467 address: IpAddress,
9468 options: IpAddress.ListenOptions,
9469) IpAddress.ListenError!net.Server {
9127 _ = userdata;9470 _ = userdata;
9128 _ = address;9471 _ = address;
9129 _ = options;9472 _ = options;
...@@ -9193,24 +9536,24 @@ fn netListenUnixWindows(...@@ -9193,24 +9536,24 @@ fn netListenUnixWindows(
9193 var storage: WsaAddress = undefined;9536 var storage: WsaAddress = undefined;
9194 const addr_len = addressUnixToWsa(address, &storage);9537 const addr_len = addressUnixToWsa(address, &storage);
91959538
9196 const syscall: Syscall = try .start();9539 var syscall: Syscall = try .start();
9197 while (true) {9540 while (true) {
9198 const rc = ws2_32.bind(socket_handle, &storage.any, addr_len);9541 const rc = ws2_32.bind(socket_handle, &storage.any, addr_len);
9199 if (rc != ws2_32.SOCKET_ERROR) break;9542 if (rc != ws2_32.SOCKET_ERROR) break;
9200 switch (ws2_32.WSAGetLastError()) {9543 switch (ws2_32.WSAGetLastError()) {
9201 .EINTR => {
9202 try syscall.checkCancel();
9203 continue;
9204 },
9205 .NOTINITIALISED => {9544 .NOTINITIALISED => {
9545 syscall.finish();
9206 try initializeWsa(t);9546 try initializeWsa(t);
9547 syscall = try .start();
9548 continue;
9549 },
9550 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
9207 try syscall.checkCancel();9551 try syscall.checkCancel();
9208 continue;9552 continue;
9209 },9553 },
9210 else => |e| {9554 else => |e| {
9211 syscall.finish();9555 syscall.finish();
9212 switch (e) {9556 switch (e) {
9213 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
9214 .EADDRINUSE => return error.AddressInUse,9557 .EADDRINUSE => return error.AddressInUse,
9215 .EADDRNOTAVAIL => return error.AddressUnavailable,9558 .EADDRNOTAVAIL => return error.AddressUnavailable,
9216 .ENOTSOCK => |err| return wsaErrorBug(err),9559 .ENOTSOCK => |err| return wsaErrorBug(err),
...@@ -9232,15 +9575,16 @@ fn netListenUnixWindows(...@@ -9232,15 +9575,16 @@ fn netListenUnixWindows(
9232 return socket_handle;9575 return socket_handle;
9233 }9576 }
9234 switch (ws2_32.WSAGetLastError()) {9577 switch (ws2_32.WSAGetLastError()) {
9235 .EINTR => continue,9578 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => continue,
9236 .NOTINITIALISED => {9579 .NOTINITIALISED => {
9580 syscall.finish();
9237 try initializeWsa(t);9581 try initializeWsa(t);
9582 syscall = try .start();
9238 continue;9583 continue;
9239 },9584 },
9240 else => |e| {9585 else => |e| {
9241 syscall.finish();9586 syscall.finish();
9242 switch (e) {9587 switch (e) {
9243 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
9244 .ENETDOWN => return error.NetworkDown,9588 .ENETDOWN => return error.NetworkDown,
9245 .EADDRINUSE => return error.AddressInUse,9589 .EADDRINUSE => return error.AddressInUse,
9246 .EISCONN => |err| return wsaErrorBug(err),9590 .EISCONN => |err| return wsaErrorBug(err),
...@@ -9469,7 +9813,7 @@ fn wsaGetSockName(...@@ -9469,7 +9813,7 @@ fn wsaGetSockName(
9469 addr: *ws2_32.sockaddr,9813 addr: *ws2_32.sockaddr,
9470 addr_len: *i32,9814 addr_len: *i32,
9471) !void {9815) !void {
9472 const syscall: Syscall = try .start();9816 var syscall: Syscall = try .start();
9473 while (true) {9817 while (true) {
9474 const rc = ws2_32.getsockname(handle, addr, addr_len);9818 const rc = ws2_32.getsockname(handle, addr, addr_len);
9475 if (rc != ws2_32.SOCKET_ERROR) {9819 if (rc != ws2_32.SOCKET_ERROR) {
...@@ -9477,19 +9821,19 @@ fn wsaGetSockName(...@@ -9477,19 +9821,19 @@ fn wsaGetSockName(
9477 return;9821 return;
9478 }9822 }
9479 switch (ws2_32.WSAGetLastError()) {9823 switch (ws2_32.WSAGetLastError()) {
9480 .EINTR => {9824 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
9481 try syscall.checkCancel();9825 try syscall.checkCancel();
9482 continue;9826 continue;
9483 },9827 },
9484 .NOTINITIALISED => {9828 .NOTINITIALISED => {
9829 syscall.finish();
9485 try initializeWsa(t);9830 try initializeWsa(t);
9486 try syscall.checkCancel();9831 syscall = try .start();
9487 continue;9832 continue;
9488 },9833 },
9489 else => |e| {9834 else => |e| {
9490 syscall.finish();9835 syscall.finish();
9491 switch (e) {9836 switch (e) {
9492 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
9493 .ENETDOWN => return error.NetworkDown,9837 .ENETDOWN => return error.NetworkDown,
9494 .EFAULT => |err| return wsaErrorBug(err),9838 .EFAULT => |err| return wsaErrorBug(err),
9495 .ENOTSOCK => |err| return wsaErrorBug(err),9839 .ENOTSOCK => |err| return wsaErrorBug(err),
...@@ -9530,21 +9874,30 @@ fn setSocketOption(fd: posix.fd_t, level: i32, opt_name: u32, option: u32) !void...@@ -9530,21 +9874,30 @@ fn setSocketOption(fd: posix.fd_t, level: i32, opt_name: u32, option: u32) !void
95309874
9531fn setSocketOptionWsa(t: *Threaded, socket: Io.net.Socket.Handle, level: i32, opt_name: u32, option: u32) !void {9875fn setSocketOptionWsa(t: *Threaded, socket: Io.net.Socket.Handle, level: i32, opt_name: u32, option: u32) !void {
9532 const o: []const u8 = @ptrCast(&option);9876 const o: []const u8 = @ptrCast(&option);
9877 var syscall: Syscall = try .start();
9533 const rc = ws2_32.setsockopt(socket, level, @bitCast(opt_name), o.ptr, @intCast(o.len));9878 const rc = ws2_32.setsockopt(socket, level, @bitCast(opt_name), o.ptr, @intCast(o.len));
9534 while (true) {9879 while (true) {
9535 if (rc != ws2_32.SOCKET_ERROR) return;9880 if (rc != ws2_32.SOCKET_ERROR) return syscall.finish();
9536 switch (ws2_32.WSAGetLastError()) {9881 switch (ws2_32.WSAGetLastError()) {
9537 .EINTR => continue,9882 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
9538 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,9883 try syscall.checkCancel();
9884 continue;
9885 },
9539 .NOTINITIALISED => {9886 .NOTINITIALISED => {
9887 syscall.finish();
9540 try initializeWsa(t);9888 try initializeWsa(t);
9889 syscall = try .start();
9541 continue;9890 continue;
9542 },9891 },
9543 .ENETDOWN => return error.NetworkDown,9892 .ENETDOWN => return syscall.fail(error.NetworkDown),
9544 .EFAULT => |err| return wsaErrorBug(err),9893 .EFAULT, .ENOTSOCK, .EINVAL => |err| {
9545 .ENOTSOCK => |err| return wsaErrorBug(err),9894 syscall.finish();
9546 .EINVAL => |err| return wsaErrorBug(err),9895 return wsaErrorBug(err);
9547 else => |err| return windows.unexpectedWSAError(err),9896 },
9897 else => |err| {
9898 syscall.finish();
9899 return windows.unexpectedWSAError(err);
9900 },
9548 }9901 }
9549 }9902 }
9550}9903}
...@@ -9592,7 +9945,7 @@ fn netConnectIpWindows(...@@ -9592,7 +9945,7 @@ fn netConnectIpWindows(
9592 var storage: WsaAddress = undefined;9945 var storage: WsaAddress = undefined;
9593 var addr_len = addressToWsa(address, &storage);9946 var addr_len = addressToWsa(address, &storage);
95949947
9595 const syscall: Syscall = try .start();9948 var syscall: Syscall = try .start();
9596 while (true) {9949 while (true) {
9597 const rc = ws2_32.connect(socket_handle, &storage.any, addr_len);9950 const rc = ws2_32.connect(socket_handle, &storage.any, addr_len);
9598 if (rc != ws2_32.SOCKET_ERROR) {9951 if (rc != ws2_32.SOCKET_ERROR) {
...@@ -9600,19 +9953,19 @@ fn netConnectIpWindows(...@@ -9600,19 +9953,19 @@ fn netConnectIpWindows(
9600 break;9953 break;
9601 }9954 }
9602 switch (ws2_32.WSAGetLastError()) {9955 switch (ws2_32.WSAGetLastError()) {
9603 .EINTR => {9956 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
9604 try syscall.checkCancel();9957 try syscall.checkCancel();
9605 continue;9958 continue;
9606 },9959 },
9607 .NOTINITIALISED => {9960 .NOTINITIALISED => {
9961 syscall.finish();
9608 try initializeWsa(t);9962 try initializeWsa(t);
9609 try syscall.checkCancel();9963 syscall = try .start();
9610 continue;9964 continue;
9611 },9965 },
9612 else => |e| {9966 else => |e| {
9613 syscall.finish();9967 syscall.finish();
9614 switch (e) {9968 switch (e) {
9615 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
9616 .EADDRNOTAVAIL => return error.AddressUnavailable,9969 .EADDRNOTAVAIL => return error.AddressUnavailable,
9617 .ECONNREFUSED => return error.ConnectionRefused,9970 .ECONNREFUSED => return error.ConnectionRefused,
9618 .ECONNRESET => return error.ConnectionResetByPeer,9971 .ECONNRESET => return error.ConnectionResetByPeer,
...@@ -9682,27 +10035,36 @@ fn netConnectUnixWindows(...@@ -9682,27 +10035,36 @@ fn netConnectUnixWindows(
9682 var storage: WsaAddress = undefined;10035 var storage: WsaAddress = undefined;
9683 const addr_len = addressUnixToWsa(address, &storage);10036 const addr_len = addressUnixToWsa(address, &storage);
968410037
10038 var syscall: Syscall = try .start();
9685 while (true) {10039 while (true) {
9686 const rc = ws2_32.connect(socket_handle, &storage.any, addr_len);10040 const rc = ws2_32.connect(socket_handle, &storage.any, addr_len);
9687 if (rc != ws2_32.SOCKET_ERROR) break;10041 if (rc != ws2_32.SOCKET_ERROR) break;
9688 switch (ws2_32.WSAGetLastError()) {10042 switch (ws2_32.WSAGetLastError()) {
9689 .EINTR => continue,10043 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
9690 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,10044 try syscall.checkCancel();
10045 continue;
10046 },
9691 .NOTINITIALISED => {10047 .NOTINITIALISED => {
10048 syscall.finish();
9692 try initializeWsa(t);10049 try initializeWsa(t);
10050 syscall = try .start();
9693 continue;10051 continue;
9694 },10052 },
969510053 else => |e| {
9696 .ECONNREFUSED => return error.FileNotFound,10054 syscall.finish();
9697 .EFAULT => |err| return wsaErrorBug(err),10055 switch (e) {
9698 .EINVAL => |err| return wsaErrorBug(err),10056 .ECONNREFUSED => return error.FileNotFound,
9699 .EISCONN => |err| return wsaErrorBug(err),10057 .EFAULT => |err| return wsaErrorBug(err),
9700 .ENOTSOCK => |err| return wsaErrorBug(err),10058 .EINVAL => |err| return wsaErrorBug(err),
9701 .EWOULDBLOCK => return error.WouldBlock,10059 .EISCONN => |err| return wsaErrorBug(err),
9702 .EACCES => return error.AccessDenied,10060 .ENOTSOCK => |err| return wsaErrorBug(err),
9703 .ENOBUFS => return error.SystemResources,10061 .EWOULDBLOCK => return error.WouldBlock,
9704 .EAFNOSUPPORT => return error.AddressFamilyUnsupported,10062 .EACCES => return error.AccessDenied,
9705 else => |err| return windows.unexpectedWSAError(err),10063 .ENOBUFS => return error.SystemResources,
10064 .EAFNOSUPPORT => return error.AddressFamilyUnsupported,
10065 else => |err| return windows.unexpectedWSAError(err),
10066 }
10067 },
9706 }10068 }
9707 }10069 }
970810070
...@@ -9756,7 +10118,7 @@ fn netBindIpWindows(...@@ -9756,7 +10118,7 @@ fn netBindIpWindows(
9756 var storage: WsaAddress = undefined;10118 var storage: WsaAddress = undefined;
9757 var addr_len = addressToWsa(address, &storage);10119 var addr_len = addressToWsa(address, &storage);
975810120
9759 const syscall: Syscall = try .start();10121 var syscall: Syscall = try .start();
9760 while (true) {10122 while (true) {
9761 const rc = ws2_32.bind(socket_handle, &storage.any, addr_len);10123 const rc = ws2_32.bind(socket_handle, &storage.any, addr_len);
9762 if (rc != ws2_32.SOCKET_ERROR) {10124 if (rc != ws2_32.SOCKET_ERROR) {
...@@ -9764,19 +10126,19 @@ fn netBindIpWindows(...@@ -9764,19 +10126,19 @@ fn netBindIpWindows(
9764 break;10126 break;
9765 }10127 }
9766 switch (ws2_32.WSAGetLastError()) {10128 switch (ws2_32.WSAGetLastError()) {
9767 .EINTR => {10129 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
9768 try syscall.checkCancel();10130 try syscall.checkCancel();
9769 continue;10131 continue;
9770 },10132 },
9771 .NOTINITIALISED => {10133 .NOTINITIALISED => {
10134 syscall.finish();
9772 try initializeWsa(t);10135 try initializeWsa(t);
9773 try syscall.checkCancel();10136 syscall = try .start();
9774 continue;10137 continue;
9775 },10138 },
9776 else => |e| {10139 else => |e| {
9777 syscall.finish();10140 syscall.finish();
9778 switch (e) {10141 switch (e) {
9779 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
9780 .EADDRINUSE => return error.AddressInUse,10142 .EADDRINUSE => return error.AddressInUse,
9781 .EADDRNOTAVAIL => return error.AddressUnavailable,10143 .EADDRNOTAVAIL => return error.AddressUnavailable,
9782 .ENOTSOCK => |err| return wsaErrorBug(err),10144 .ENOTSOCK => |err| return wsaErrorBug(err),
...@@ -9886,7 +10248,7 @@ fn openSocketWsa(...@@ -9886,7 +10248,7 @@ fn openSocketWsa(
9886 const mode = posixSocketMode(options.mode);10248 const mode = posixSocketMode(options.mode);
9887 const protocol = posixProtocol(options.protocol);10249 const protocol = posixProtocol(options.protocol);
9888 const flags: u32 = ws2_32.WSA_FLAG_OVERLAPPED | ws2_32.WSA_FLAG_NO_HANDLE_INHERIT;10250 const flags: u32 = ws2_32.WSA_FLAG_OVERLAPPED | ws2_32.WSA_FLAG_NO_HANDLE_INHERIT;
9889 const syscall: Syscall = try .start();10251 var syscall: Syscall = try .start();
9890 while (true) {10252 while (true) {
9891 const rc = ws2_32.WSASocketW(family, @bitCast(mode), @bitCast(protocol), null, 0, flags);10253 const rc = ws2_32.WSASocketW(family, @bitCast(mode), @bitCast(protocol), null, 0, flags);
9892 if (rc != ws2_32.INVALID_SOCKET) {10254 if (rc != ws2_32.INVALID_SOCKET) {
...@@ -9894,19 +10256,19 @@ fn openSocketWsa(...@@ -9894,19 +10256,19 @@ fn openSocketWsa(
9894 return rc;10256 return rc;
9895 }10257 }
9896 switch (ws2_32.WSAGetLastError()) {10258 switch (ws2_32.WSAGetLastError()) {
9897 .EINTR => {10259 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
9898 try syscall.checkCancel();10260 try syscall.checkCancel();
9899 continue;10261 continue;
9900 },10262 },
9901 .NOTINITIALISED => {10263 .NOTINITIALISED => {
10264 syscall.finish();
9902 try initializeWsa(t);10265 try initializeWsa(t);
9903 try syscall.checkCancel();10266 syscall = try .start();
9904 continue;10267 continue;
9905 },10268 },
9906 else => |e| {10269 else => |e| {
9907 syscall.finish();10270 syscall.finish();
9908 switch (e) {10271 switch (e) {
9909 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
9910 .EAFNOSUPPORT => return error.AddressFamilyUnsupported,10272 .EAFNOSUPPORT => return error.AddressFamilyUnsupported,
9911 .EMFILE => return error.ProcessFdQuotaExceeded,10273 .EMFILE => return error.ProcessFdQuotaExceeded,
9912 .ENOBUFS => return error.SystemResources,10274 .ENOBUFS => return error.SystemResources,
...@@ -9984,7 +10346,7 @@ fn netAcceptWindows(userdata: ?*anyopaque, listen_handle: net.Socket.Handle) net...@@ -9984,7 +10346,7 @@ fn netAcceptWindows(userdata: ?*anyopaque, listen_handle: net.Socket.Handle) net
9984 const t: *Threaded = @ptrCast(@alignCast(userdata));10346 const t: *Threaded = @ptrCast(@alignCast(userdata));
9985 var storage: WsaAddress = undefined;10347 var storage: WsaAddress = undefined;
9986 var addr_len: i32 = @sizeOf(WsaAddress);10348 var addr_len: i32 = @sizeOf(WsaAddress);
9987 const syscall: Syscall = try .start();10349 var syscall: Syscall = try .start();
9988 while (true) {10350 while (true) {
9989 const rc = ws2_32.accept(listen_handle, &storage.any, &addr_len);10351 const rc = ws2_32.accept(listen_handle, &storage.any, &addr_len);
9990 if (rc != ws2_32.INVALID_SOCKET) {10352 if (rc != ws2_32.INVALID_SOCKET) {
...@@ -9995,19 +10357,19 @@ fn netAcceptWindows(userdata: ?*anyopaque, listen_handle: net.Socket.Handle) net...@@ -9995,19 +10357,19 @@ fn netAcceptWindows(userdata: ?*anyopaque, listen_handle: net.Socket.Handle) net
9995 } };10357 } };
9996 }10358 }
9997 switch (ws2_32.WSAGetLastError()) {10359 switch (ws2_32.WSAGetLastError()) {
9998 .EINTR => {10360 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
9999 try syscall.checkCancel();10361 try syscall.checkCancel();
10000 continue;10362 continue;
10001 },10363 },
10002 .NOTINITIALISED => {10364 .NOTINITIALISED => {
10365 syscall.finish();
10003 try initializeWsa(t);10366 try initializeWsa(t);
10004 try syscall.checkCancel();10367 syscall = try .start();
10005 continue;10368 continue;
10006 },10369 },
10007 else => |e| {10370 else => |e| {
10008 syscall.finish();10371 syscall.finish();
10009 switch (e) {10372 switch (e) {
10010 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
10011 .ECONNRESET => return error.ConnectionAborted,10373 .ECONNRESET => return error.ConnectionAborted,
10012 .EFAULT => |err| return wsaErrorBug(err),10374 .EFAULT => |err| return wsaErrorBug(err),
10013 .ENOTSOCK => |err| return wsaErrorBug(err),10375 .ENOTSOCK => |err| return wsaErrorBug(err),
...@@ -10141,48 +10503,41 @@ fn netReadWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, data: [][]u8...@@ -10141,48 +10503,41 @@ fn netReadWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, data: [][]u8
10141 break :b bufs;10503 break :b bufs;
10142 };10504 };
1014310505
10506 var syscall: Syscall = try .start();
10144 while (true) {10507 while (true) {
10145 try Thread.checkCancel();
10146
10147 var flags: u32 = 0;10508 var flags: u32 = 0;
10148 var overlapped: windows.OVERLAPPED = std.mem.zeroes(windows.OVERLAPPED);
10149 var n: u32 = undefined;10509 var n: u32 = undefined;
10150 const rc = ws2_32.WSARecv(handle, bufs.ptr, @intCast(bufs.len), &n, &flags, &overlapped, null);10510 const rc = ws2_32.WSARecv(handle, bufs.ptr, @intCast(bufs.len), &n, &flags, null, null);
10151 if (rc != ws2_32.SOCKET_ERROR) return n;10511 if (rc != ws2_32.SOCKET_ERROR) {
10152 const wsa_error: ws2_32.WinsockError = switch (ws2_32.WSAGetLastError()) {10512 syscall.finish();
10153 .IO_PENDING => e: {10513 return n;
10154 var result_flags: u32 = undefined;10514 }
10155 const overlapped_rc = ws2_32.WSAGetOverlappedResult(10515 switch (ws2_32.WSAGetLastError()) {
10156 handle,10516 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
10157 &overlapped,10517 try syscall.checkCancel();
10158 &n,10518 continue;
10159 windows.TRUE,
10160 &result_flags,
10161 );
10162 if (overlapped_rc == windows.FALSE) {
10163 break :e ws2_32.WSAGetLastError();
10164 } else {
10165 return n;
10166 }
10167 },10519 },
10168 else => |err| err,
10169 };
10170 switch (wsa_error) {
10171 .EINTR => continue,
10172 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
10173 .NOTINITIALISED => {10520 .NOTINITIALISED => {
10521 syscall.finish();
10174 try initializeWsa(t);10522 try initializeWsa(t);
10523 syscall = try .start();
10175 continue;10524 continue;
10176 },10525 },
1017710526
10178 .ECONNRESET => return error.ConnectionResetByPeer,10527 .ECONNRESET => return syscall.fail(error.ConnectionResetByPeer),
10528 .ENETDOWN => return syscall.fail(error.NetworkDown),
10529 .ENETRESET => return syscall.fail(error.ConnectionResetByPeer),
10530 .ENOTCONN => return syscall.fail(error.SocketUnconnected),
10179 .EFAULT => unreachable, // a pointer is not completely contained in user address space.10531 .EFAULT => unreachable, // a pointer is not completely contained in user address space.
10180 .EINVAL => |err| return wsaErrorBug(err),10532
10181 .EMSGSIZE => |err| return wsaErrorBug(err),10533 else => |err| {
10182 .ENETDOWN => return error.NetworkDown,10534 syscall.finish();
10183 .ENETRESET => return error.ConnectionResetByPeer,10535 switch (err) {
10184 .ENOTCONN => return error.SocketUnconnected,10536 .EINVAL => return wsaErrorBug(err),
10185 else => |err| return windows.unexpectedWSAError(err),10537 .EMSGSIZE => return wsaErrorBug(err),
10538 else => return windows.unexpectedWSAError(err),
10539 }
10540 },
10186 }10541 }
10187 }10542 }
10188}10543}
...@@ -10269,7 +10624,7 @@ fn netSendOne(...@@ -10269,7 +10624,7 @@ fn netSendOne(
10269 .controllen = @intCast(message.control.len),10624 .controllen = @intCast(message.control.len),
10270 .flags = 0,10625 .flags = 0,
10271 };10626 };
10272 const syscall: Syscall = try .start();10627 var syscall: Syscall = try .start();
10273 while (true) {10628 while (true) {
10274 const rc = posix.system.sendmsg(handle, &msg, flags);10629 const rc = posix.system.sendmsg(handle, &msg, flags);
10275 if (is_windows) {10630 if (is_windows) {
...@@ -10279,19 +10634,19 @@ fn netSendOne(...@@ -10279,19 +10634,19 @@ fn netSendOne(
10279 return;10634 return;
10280 }10635 }
10281 switch (ws2_32.WSAGetLastError()) {10636 switch (ws2_32.WSAGetLastError()) {
10282 .EINTR => {10637 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
10283 try syscall.checkCancel();10638 try syscall.checkCancel();
10284 continue;10639 continue;
10285 },10640 },
10286 .NOTINITIALISED => {10641 .NOTINITIALISED => {
10642 syscall.finish();
10287 try initializeWsa(t);10643 try initializeWsa(t);
10288 try syscall.checkCancel();10644 syscall = try .start();
10289 continue;10645 continue;
10290 },10646 },
10291 else => |e| {10647 else => |e| {
10292 syscall.finish();10648 syscall.finish();
10293 switch (e) {10649 switch (e) {
10294 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
10295 .EACCES => return error.AccessDenied,10650 .EACCES => return error.AccessDenied,
10296 .EADDRNOTAVAIL => return error.AddressUnavailable,10651 .EADDRNOTAVAIL => return error.AddressUnavailable,
10297 .ECONNRESET => return error.ConnectionResetByPeer,10652 .ECONNRESET => return error.ConnectionResetByPeer,
...@@ -10729,49 +11084,44 @@ fn netWriteWindows(...@@ -10729,49 +11084,44 @@ fn netWriteWindows(
10729 },11084 },
10730 };11085 };
1073111086
11087 var syscall: Syscall = try .start();
10732 while (true) {11088 while (true) {
10733 try Thread.checkCancel();
10734
10735 var n: u32 = undefined;11089 var n: u32 = undefined;
10736 var overlapped: windows.OVERLAPPED = std.mem.zeroes(windows.OVERLAPPED);11090 const rc = ws2_32.WSASend(handle, &iovecs, len, &n, 0, null, null);
10737 const rc = ws2_32.WSASend(handle, &iovecs, len, &n, 0, &overlapped, null);11091 if (rc != ws2_32.SOCKET_ERROR) {
10738 if (rc != ws2_32.SOCKET_ERROR) return n;11092 syscall.finish();
10739 const wsa_error: ws2_32.WinsockError = switch (ws2_32.WSAGetLastError()) {11093 return n;
10740 .IO_PENDING => e: {11094 }
10741 var result_flags: u32 = undefined;11095 switch (ws2_32.WSAGetLastError()) {
10742 const overlapped_rc = ws2_32.WSAGetOverlappedResult(11096 .IO_PENDING => unreachable, // not overlapped
10743 handle,11097 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
10744 &overlapped,11098 try syscall.checkCancel();
10745 &n,11099 continue;
10746 windows.TRUE,
10747 &result_flags,
10748 );
10749 if (overlapped_rc == windows.FALSE) {
10750 break :e ws2_32.WSAGetLastError();
10751 } else {
10752 return n;
10753 }
10754 },11100 },
10755 else => |err| err,
10756 };
10757 switch (wsa_error) {
10758 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => continue,
10759 .NOTINITIALISED => {11101 .NOTINITIALISED => {
11102 syscall.finish();
10760 try initializeWsa(t);11103 try initializeWsa(t);
11104 syscall = try .start();
10761 continue;11105 continue;
10762 },11106 },
1076311107
10764 .ECONNABORTED => return error.ConnectionResetByPeer,11108 .ECONNABORTED => return syscall.fail(error.ConnectionResetByPeer),
10765 .ECONNRESET => return error.ConnectionResetByPeer,11109 .ECONNRESET => return syscall.fail(error.ConnectionResetByPeer),
10766 .EINVAL => return error.SocketUnconnected,11110 .EINVAL => return syscall.fail(error.SocketUnconnected),
10767 .ENETDOWN => return error.NetworkDown,11111 .ENETDOWN => return syscall.fail(error.NetworkDown),
10768 .ENETRESET => return error.ConnectionResetByPeer,11112 .ENETRESET => return syscall.fail(error.ConnectionResetByPeer),
10769 .ENOBUFS => return error.SystemResources,11113 .ENOBUFS => return syscall.fail(error.SystemResources),
10770 .ENOTCONN => return error.SocketUnconnected,11114 .ENOTCONN => return syscall.fail(error.SocketUnconnected),
10771 .ENOTSOCK => |err| return wsaErrorBug(err),11115
10772 .EOPNOTSUPP => |err| return wsaErrorBug(err),11116 else => |err| {
10773 .ESHUTDOWN => |err| return wsaErrorBug(err),11117 syscall.finish();
10774 else => |err| return windows.unexpectedWSAError(err),11118 switch (err) {
11119 .ENOTSOCK => return wsaErrorBug(err),
11120 .EOPNOTSUPP => return wsaErrorBug(err),
11121 .ESHUTDOWN => return wsaErrorBug(err),
11122 else => return windows.unexpectedWSAError(err),
11123 }
11124 },
10775 }11125 }
10776 }11126 }
10777}11127}
...@@ -10872,7 +11222,6 @@ fn netShutdownPosix(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.S...@@ -10872,7 +11222,6 @@ fn netShutdownPosix(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.S
10872fn netShutdownWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.ShutdownHow) net.ShutdownError!void {11222fn netShutdownWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.ShutdownHow) net.ShutdownError!void {
10873 if (!have_networking) return error.NetworkDown;11223 if (!have_networking) return error.NetworkDown;
10874 const t: *Threaded = @ptrCast(@alignCast(userdata));11224 const t: *Threaded = @ptrCast(@alignCast(userdata));
10875 const current_thread = Thread.getCurrent(t);
1087611225
10877 const wsa_how: i32 = switch (how) {11226 const wsa_how: i32 = switch (how) {
10878 .recv => ws2_32.SD_RECEIVE,11227 .recv => ws2_32.SD_RECEIVE,
...@@ -10880,27 +11229,27 @@ fn netShutdownWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net...@@ -10880,27 +11229,27 @@ fn netShutdownWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net
10880 .both => ws2_32.SD_BOTH,11229 .both => ws2_32.SD_BOTH,
10881 };11230 };
1088211231
10883 try current_thread.beginSyscall();11232 var syscall: Syscall = try .start();
10884 while (true) {11233 while (true) {
10885 const rc = ws2_32.shutdown(handle, wsa_how);11234 const rc = ws2_32.shutdown(handle, wsa_how);
10886 if (rc != ws2_32.SOCKET_ERROR) {11235 if (rc != ws2_32.SOCKET_ERROR) {
10887 current_thread.endSyscall();11236 syscall.finish();
10888 return;11237 return;
10889 }11238 }
10890 switch (ws2_32.WSAGetLastError()) {11239 switch (ws2_32.WSAGetLastError()) {
10891 .EINTR => {11240 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
10892 try current_thread.checkCancel();11241 try syscall.checkCancel();
10893 continue;11242 continue;
10894 },11243 },
10895 .NOTINITIALISED => {11244 .NOTINITIALISED => {
11245 syscall.finish();
10896 try initializeWsa(t);11246 try initializeWsa(t);
10897 try current_thread.checkCancel();11247 syscall = try .start();
10898 continue;11248 continue;
10899 },11249 },
10900 else => |e| {11250 else => |e| {
10901 current_thread.endSyscall();11251 syscall.finish();
10902 switch (e) {11252 switch (e) {
10903 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
10904 .ECONNABORTED => return error.ConnectionAborted,11253 .ECONNABORTED => return error.ConnectionAborted,
10905 .ECONNRESET => return error.ConnectionResetByPeer,11254 .ECONNRESET => return error.ConnectionResetByPeer,
10906 .ENETDOWN => return error.NetworkDown,11255 .ENETDOWN => return error.NetworkDown,
...@@ -11093,18 +11442,17 @@ fn netLookupFallible(...@@ -11093,18 +11442,17 @@ fn netLookupFallible(
11093 .provider = null,11442 .provider = null,
11094 .next = null,11443 .next = null,
11095 };11444 };
11096 const cancel_handle: ?*windows.HANDLE = null;
11097 var res: *ws2_32.ADDRINFOEXW = undefined;11445 var res: *ws2_32.ADDRINFOEXW = undefined;
11098 const timeout: ?*ws2_32.timeval = null;11446 const timeout: ?*ws2_32.timeval = null;
11099 while (true) {11447 while (true) {
11448 // TODO: hook this up to cancelation with `Thread.Status.cancelation.blocked_windows_dns`.
11449 // See matching TODO in `Thread.cancelAwaitable`.
11100 try Thread.checkCancel();11450 try Thread.checkCancel();
11101 // TODO make this append to the queue eagerly rather than blocking until11451 // TODO make this append to the queue eagerly rather than blocking until the whole thing finishes
11102 // the whole thing finishes11452 const rc: ws2_32.WinsockError = @enumFromInt(ws2_32.GetAddrInfoExW(name_w, port_w, .DNS, null, &hints, &res, timeout, null, null, null));
11103 const rc: ws2_32.WinsockError = @enumFromInt(ws2_32.GetAddrInfoExW(name_w, port_w, .DNS, null, &hints, &res, timeout, null, null, cancel_handle));
11104 switch (rc) {11453 switch (rc) {
11105 @as(ws2_32.WinsockError, @enumFromInt(0)) => break,11454 @as(ws2_32.WinsockError, @enumFromInt(0)) => break,
11106 .EINTR => continue,11455 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => continue,
11107 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
11108 .NOTINITIALISED => {11456 .NOTINITIALISED => {
11109 try initializeWsa(t);11457 try initializeWsa(t);
11110 continue;11458 continue;
...@@ -11352,29 +11700,33 @@ fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) std.process.SetCurrentD...@@ -11352,29 +11700,33 @@ fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) std.process.SetCurrentD
11352 _ = t;11700 _ = t;
1135311701
11354 if (is_windows) {11702 if (is_windows) {
11355 try Thread.checkCancel();
11356 var dir_path_buffer: [windows.PATH_MAX_WIDE]u16 = undefined;11703 var dir_path_buffer: [windows.PATH_MAX_WIDE]u16 = undefined;
11357 // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks11704 // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks
11705 try Thread.checkCancel();
11358 const dir_path = try windows.GetFinalPathNameByHandle(dir.handle, .{}, &dir_path_buffer);11706 const dir_path = try windows.GetFinalPathNameByHandle(dir.handle, .{}, &dir_path_buffer);
11359 const path_len_bytes = std.math.cast(u16, dir_path.len * 2) orelse return error.NameTooLong;11707 const path_len_bytes = std.math.cast(u16, dir_path.len * 2) orelse return error.NameTooLong;
11360 try Thread.checkCancel();
11361 var nt_name: windows.UNICODE_STRING = .{11708 var nt_name: windows.UNICODE_STRING = .{
11362 .Length = path_len_bytes,11709 .Length = path_len_bytes,
11363 .MaximumLength = path_len_bytes,11710 .MaximumLength = path_len_bytes,
11364 .Buffer = @constCast(dir_path.ptr),11711 .Buffer = @constCast(dir_path.ptr),
11365 };11712 };
11366 switch (windows.ntdll.RtlSetCurrentDirectory_U(&nt_name)) {11713 const syscall: Syscall = try .start();
11367 .SUCCESS => return,11714 while (true) switch (windows.ntdll.RtlSetCurrentDirectory_U(&nt_name)) {
11368 .OBJECT_NAME_INVALID => return error.BadPathName,11715 .SUCCESS => return syscall.finish(),
11369 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,11716 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
11370 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,11717 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
11371 .NO_MEDIA_IN_DEVICE => return error.NoDevice,11718 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
11372 .INVALID_PARAMETER => |err| return windows.statusBug(err),11719 .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice),
11373 .ACCESS_DENIED => return error.AccessDenied,11720 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),
11374 .OBJECT_PATH_SYNTAX_BAD => |err| return windows.statusBug(err),11721 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
11375 .NOT_A_DIRECTORY => return error.NotDir,11722 .OBJECT_PATH_SYNTAX_BAD => |err| return syscall.ntstatusBug(err),
11376 else => |status| return windows.unexpectedStatus(status),11723 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
11377 }11724 .CANCELLED => {
11725 try syscall.checkCancel();
11726 continue;
11727 },
11728 else => |status| return syscall.unexpectedNtstatus(status),
11729 };
11378 }11730 }
1137911731
11380 if (dir.handle == posix.AT.FDCWD) return;11732 if (dir.handle == posix.AT.FDCWD) return;
...@@ -12185,391 +12537,6 @@ fn initializeWsa(t: *Threaded) error{ NetworkDown, Canceled }!void {...@@ -12185,391 +12537,6 @@ fn initializeWsa(t: *Threaded) error{ NetworkDown, Canceled }!void {
1218512537
12186fn doNothingSignalHandler(_: posix.SIG) callconv(.c) void {}12538fn doNothingSignalHandler(_: posix.SIG) callconv(.c) void {}
1218712539
12188const pthreads_futex = struct {
12189 const c = std.c;
12190 const atomic = std.atomic;
12191
12192 const Event = struct {
12193 cond: c.pthread_cond_t,
12194 mutex: c.pthread_mutex_t,
12195 state: enum { empty, waiting, notified },
12196
12197 fn init(self: *Event) void {
12198 // Use static init instead of pthread_cond/mutex_init() since this is generally faster.
12199 self.cond = .{};
12200 self.mutex = .{};
12201 self.state = .empty;
12202 }
12203
12204 fn deinit(self: *Event) void {
12205 // Some platforms reportedly give EINVAL for statically initialized pthread types.
12206 const rc = c.pthread_cond_destroy(&self.cond);
12207 assert(rc == .SUCCESS or rc == .INVAL);
12208
12209 const rm = c.pthread_mutex_destroy(&self.mutex);
12210 assert(rm == .SUCCESS or rm == .INVAL);
12211
12212 self.* = undefined;
12213 }
12214
12215 fn wait(self: *Event, timeout: ?u64) error{Timeout}!void {
12216 assert(c.pthread_mutex_lock(&self.mutex) == .SUCCESS);
12217 defer assert(c.pthread_mutex_unlock(&self.mutex) == .SUCCESS);
12218
12219 // Early return if the event was already set.
12220 if (self.state == .notified) {
12221 return;
12222 }
12223
12224 // Compute the absolute timeout if one was specified.
12225 // POSIX requires that REALTIME is used by default for the pthread timedwait functions.
12226 // This can be changed with pthread_condattr_setclock, but it's an extension and may not be available everywhere.
12227 var ts: c.timespec = undefined;
12228 if (timeout) |timeout_ns| {
12229 ts = std.posix.clock_gettime(c.CLOCK.REALTIME) catch return error.Timeout;
12230 ts.sec +|= @as(@TypeOf(ts.sec), @intCast(timeout_ns / std.time.ns_per_s));
12231 ts.nsec += @as(@TypeOf(ts.nsec), @intCast(timeout_ns % std.time.ns_per_s));
12232
12233 if (ts.nsec >= std.time.ns_per_s) {
12234 ts.sec +|= 1;
12235 ts.nsec -= std.time.ns_per_s;
12236 }
12237 }
12238
12239 // Start waiting on the event - there can be only one thread waiting.
12240 assert(self.state == .empty);
12241 self.state = .waiting;
12242
12243 while (true) {
12244 // Block using either pthread_cond_wait or pthread_cond_timewait if there's an absolute timeout.
12245 const rc = blk: {
12246 if (timeout == null) break :blk c.pthread_cond_wait(&self.cond, &self.mutex);
12247 break :blk c.pthread_cond_timedwait(&self.cond, &self.mutex, &ts);
12248 };
12249
12250 // After waking up, check if the event was set.
12251 if (self.state == .notified) {
12252 return;
12253 }
12254
12255 assert(self.state == .waiting);
12256 switch (rc) {
12257 .SUCCESS => {},
12258 .TIMEDOUT => {
12259 // If timed out, reset the event to avoid the set() thread doing an unnecessary signal().
12260 self.state = .empty;
12261 return error.Timeout;
12262 },
12263 .INVAL => recoverableOsBugDetected(), // cond, mutex, and potentially ts should all be valid
12264 .PERM => recoverableOsBugDetected(), // mutex is locked when cond_*wait() functions are called
12265 else => recoverableOsBugDetected(),
12266 }
12267 }
12268 }
12269
12270 fn set(self: *Event) void {
12271 assert(c.pthread_mutex_lock(&self.mutex) == .SUCCESS);
12272 defer assert(c.pthread_mutex_unlock(&self.mutex) == .SUCCESS);
12273
12274 // Make sure that multiple calls to set() were not done on the same Event.
12275 const old_state = self.state;
12276 assert(old_state != .notified);
12277
12278 // Mark the event as set and wake up the waiting thread if there was one.
12279 // This must be done while the mutex as the wait() thread could deallocate
12280 // the condition variable once it observes the new state, potentially causing a UAF if done unlocked.
12281 self.state = .notified;
12282 if (old_state == .waiting) {
12283 assert(c.pthread_cond_signal(&self.cond) == .SUCCESS);
12284 }
12285 }
12286 };
12287
12288 const Treap = std.Treap(usize, std.math.order);
12289 const Waiter = struct {
12290 node: Treap.Node,
12291 prev: ?*Waiter,
12292 next: ?*Waiter,
12293 tail: ?*Waiter,
12294 is_queued: bool,
12295 event: Event,
12296 };
12297
12298 // An unordered set of Waiters
12299 const WaitList = struct {
12300 top: ?*Waiter = null,
12301 len: usize = 0,
12302
12303 fn push(self: *WaitList, waiter: *Waiter) void {
12304 waiter.next = self.top;
12305 self.top = waiter;
12306 self.len += 1;
12307 }
12308
12309 fn pop(self: *WaitList) ?*Waiter {
12310 const waiter = self.top orelse return null;
12311 self.top = waiter.next;
12312 self.len -= 1;
12313 return waiter;
12314 }
12315 };
12316
12317 const WaitQueue = struct {
12318 fn insert(treap: *Treap, address: usize, waiter: *Waiter) void {
12319 // prepare the waiter to be inserted.
12320 waiter.next = null;
12321 waiter.is_queued = true;
12322
12323 // Find the wait queue entry associated with the address.
12324 // If there isn't a wait queue on the address, this waiter creates the queue.
12325 var entry = treap.getEntryFor(address);
12326 const entry_node = entry.node orelse {
12327 waiter.prev = null;
12328 waiter.tail = waiter;
12329 entry.set(&waiter.node);
12330 return;
12331 };
12332
12333 // There's a wait queue on the address; get the queue head and tail.
12334 const head: *Waiter = @fieldParentPtr("node", entry_node);
12335 const tail = head.tail orelse unreachable;
12336
12337 // Push the waiter to the tail by replacing it and linking to the previous tail.
12338 head.tail = waiter;
12339 tail.next = waiter;
12340 waiter.prev = tail;
12341 }
12342
12343 fn remove(treap: *Treap, address: usize, max_waiters: usize) WaitList {
12344 // Find the wait queue associated with this address and get the head/tail if any.
12345 var entry = treap.getEntryFor(address);
12346 var queue_head: ?*Waiter = if (entry.node) |node| @fieldParentPtr("node", node) else null;
12347 const queue_tail = if (queue_head) |head| head.tail else null;
12348
12349 // Once we're done updating the head, fix it's tail pointer and update the treap's queue head as well.
12350 defer entry.set(blk: {
12351 const new_head = queue_head orelse break :blk null;
12352 new_head.tail = queue_tail;
12353 break :blk &new_head.node;
12354 });
12355
12356 var removed = WaitList{};
12357 while (removed.len < max_waiters) {
12358 // dequeue and collect waiters from their wait queue.
12359 const waiter = queue_head orelse break;
12360 queue_head = waiter.next;
12361 removed.push(waiter);
12362
12363 // When dequeueing, we must mark is_queued as false.
12364 // This ensures that a waiter which calls tryRemove() returns false.
12365 assert(waiter.is_queued);
12366 waiter.is_queued = false;
12367 }
12368
12369 return removed;
12370 }
12371
12372 fn tryRemove(treap: *Treap, address: usize, waiter: *Waiter) bool {
12373 if (!waiter.is_queued) {
12374 return false;
12375 }
12376
12377 queue_remove: {
12378 // Find the wait queue associated with the address.
12379 var entry = blk: {
12380 // A waiter without a previous link means it's the queue head that's in the treap so we can avoid lookup.
12381 if (waiter.prev == null) {
12382 assert(waiter.node.key == address);
12383 break :blk treap.getEntryForExisting(&waiter.node);
12384 }
12385 break :blk treap.getEntryFor(address);
12386 };
12387
12388 // The queue head and tail must exist if we're removing a queued waiter.
12389 const head: *Waiter = @fieldParentPtr("node", entry.node orelse unreachable);
12390 const tail = head.tail orelse unreachable;
12391
12392 // A waiter with a previous link is never the head of the queue.
12393 if (waiter.prev) |prev| {
12394 assert(waiter != head);
12395 prev.next = waiter.next;
12396
12397 // A waiter with both a previous and next link is in the middle.
12398 // We only need to update the surrounding waiter's links to remove it.
12399 if (waiter.next) |next| {
12400 assert(waiter != tail);
12401 next.prev = waiter.prev;
12402 break :queue_remove;
12403 }
12404
12405 // A waiter with a previous but no next link means it's the tail of the queue.
12406 // In that case, we need to update the head's tail reference.
12407 assert(waiter == tail);
12408 head.tail = waiter.prev;
12409 break :queue_remove;
12410 }
12411
12412 // A waiter with no previous link means it's the queue head of queue.
12413 // We must replace (or remove) the head waiter reference in the treap.
12414 assert(waiter == head);
12415 entry.set(blk: {
12416 const new_head = waiter.next orelse break :blk null;
12417 new_head.tail = head.tail;
12418 break :blk &new_head.node;
12419 });
12420 }
12421
12422 // Mark the waiter as successfully removed.
12423 waiter.is_queued = false;
12424 return true;
12425 }
12426 };
12427
12428 const Bucket = struct {
12429 mutex: c.pthread_mutex_t align(atomic.cache_line) = .{},
12430 pending: atomic.Value(usize) = atomic.Value(usize).init(0),
12431 treap: Treap = .{},
12432
12433 // Global array of buckets that addresses map to.
12434 // Bucket array size is pretty much arbitrary here, but it must be a power of two for fibonacci hashing.
12435 var buckets = [_]Bucket{.{}} ** @bitSizeOf(usize);
12436
12437 // https://github.com/Amanieu/parking_lot/blob/1cf12744d097233316afa6c8b7d37389e4211756/core/src/parking_lot.rs#L343-L353
12438 fn from(address: usize) *Bucket {
12439 // The upper `@bitSizeOf(usize)` bits of the fibonacci golden ratio.
12440 // Hashing this via (h * k) >> (64 - b) where k=golden-ration and b=bitsize-of-array
12441 // evenly lays out h=hash values over the bit range even when the hash has poor entropy (identity-hash for pointers).
12442 const max_multiplier_bits = @bitSizeOf(usize);
12443 const fibonacci_multiplier = 0x9E3779B97F4A7C15 >> (64 - max_multiplier_bits);
12444
12445 const max_bucket_bits = @ctz(buckets.len);
12446 comptime assert(std.math.isPowerOfTwo(buckets.len));
12447
12448 const index = (address *% fibonacci_multiplier) >> (max_multiplier_bits - max_bucket_bits);
12449 return &buckets[index];
12450 }
12451 };
12452
12453 const Address = struct {
12454 fn from(ptr: *const u32) usize {
12455 // Get the alignment of the pointer.
12456 const alignment = @alignOf(atomic.Value(u32));
12457 comptime assert(std.math.isPowerOfTwo(alignment));
12458
12459 // Make sure the pointer is aligned,
12460 // then cut off the zero bits from the alignment to get the unique address.
12461 const addr = @intFromPtr(ptr);
12462 assert(addr & (alignment - 1) == 0);
12463 return addr >> @ctz(@as(usize, alignment));
12464 }
12465 };
12466
12467 fn wait(ptr: *const u32, expect: u32, timeout: ?u64) error{Timeout}!void {
12468 const address = Address.from(ptr);
12469 const bucket = Bucket.from(address);
12470
12471 // Announce that there's a waiter in the bucket before checking the ptr/expect condition.
12472 // If the announcement is reordered after the ptr check, the waiter could deadlock:
12473 //
12474 // - T1: checks ptr == expect which is true
12475 // - T2: updates ptr to != expect
12476 // - T2: does Futex.wake(), sees no pending waiters, exits
12477 // - T1: bumps pending waiters (was reordered after the ptr == expect check)
12478 // - T1: goes to sleep and misses both the ptr change and T2's wake up
12479 //
12480 // acquire barrier to ensure the announcement happens before the ptr check below.
12481 var pending = bucket.pending.fetchAdd(1, .acquire);
12482 assert(pending < std.math.maxInt(usize));
12483
12484 // If the wait gets canceled, remove the pending count we previously added.
12485 // This is done outside the mutex lock to keep the critical section short in case of contention.
12486 var canceled = false;
12487 defer if (canceled) {
12488 pending = bucket.pending.fetchSub(1, .monotonic);
12489 assert(pending > 0);
12490 };
12491
12492 var waiter: Waiter = undefined;
12493 {
12494 assert(c.pthread_mutex_lock(&bucket.mutex) == .SUCCESS);
12495 defer assert(c.pthread_mutex_unlock(&bucket.mutex) == .SUCCESS);
12496
12497 canceled = @atomicLoad(u32, ptr, .monotonic) != expect;
12498 if (canceled) {
12499 return;
12500 }
12501
12502 waiter.event.init();
12503 WaitQueue.insert(&bucket.treap, address, &waiter);
12504 }
12505
12506 defer {
12507 assert(!waiter.is_queued);
12508 waiter.event.deinit();
12509 }
12510
12511 waiter.event.wait(timeout) catch {
12512 // If we fail to cancel after a timeout, it means a wake() thread
12513 // dequeued us and will wake us up. We must wait until the event is
12514 // set as that's a signal that the wake() thread won't access the
12515 // waiter memory anymore. If we return early without waiting, the
12516 // waiter on the stack would be invalidated and the wake() thread
12517 // risks a UAF.
12518 defer if (!canceled) waiter.event.wait(null) catch unreachable;
12519
12520 assert(c.pthread_mutex_lock(&bucket.mutex) == .SUCCESS);
12521 defer assert(c.pthread_mutex_unlock(&bucket.mutex) == .SUCCESS);
12522
12523 canceled = WaitQueue.tryRemove(&bucket.treap, address, &waiter);
12524 if (canceled) {
12525 return error.Timeout;
12526 }
12527 };
12528 }
12529
12530 fn wake(ptr: *const u32, max_waiters: u32) void {
12531 const address = Address.from(ptr);
12532 const bucket = Bucket.from(address);
12533
12534 // Quick check if there's even anything to wake up.
12535 // The change to the ptr's value must happen before we check for pending waiters.
12536 // If not, the wake() thread could miss a sleeping waiter and have it deadlock:
12537 //
12538 // - T2: p = has pending waiters (reordered before the ptr update)
12539 // - T1: bump pending waiters
12540 // - T1: if ptr == expected: sleep()
12541 // - T2: update ptr != expected
12542 // - T2: p is false from earlier so doesn't wake (T1 missed ptr update and T2 missed T1 sleeping)
12543 //
12544 // What we really want here is a Release load, but that doesn't exist under the C11 memory model.
12545 // We could instead do `bucket.pending.fetchAdd(0, Release) == 0` which achieves effectively the same thing,
12546 // LLVM lowers the fetchAdd(0, .release) into an mfence+load which avoids gaining ownership of the cache-line.
12547 if (bucket.pending.fetchAdd(0, .release) == 0) {
12548 return;
12549 }
12550
12551 // Keep a list of all the waiters notified and wake then up outside the mutex critical section.
12552 var notified = WaitList{};
12553 defer if (notified.len > 0) {
12554 const pending = bucket.pending.fetchSub(notified.len, .monotonic);
12555 assert(pending >= notified.len);
12556
12557 while (notified.pop()) |waiter| {
12558 assert(!waiter.is_queued);
12559 waiter.event.set();
12560 }
12561 };
12562
12563 assert(c.pthread_mutex_lock(&bucket.mutex) == .SUCCESS);
12564 defer assert(c.pthread_mutex_unlock(&bucket.mutex) == .SUCCESS);
12565
12566 // Another pending check again to avoid the WaitQueue lookup if not necessary.
12567 if (bucket.pending.load(.monotonic) > 0) {
12568 notified = WaitQueue.remove(&bucket.treap, address, max_waiters);
12569 }
12570 }
12571};
12572
12573fn scanEnviron(t: *Threaded) void {12540fn scanEnviron(t: *Threaded) void {
12574 t.mutex.lock();12541 t.mutex.lock();
12575 defer t.mutex.unlock();12542 defer t.mutex.unlock();
...@@ -12688,3 +12655,459 @@ fn scanEnviron(t: *Threaded) void {...@@ -12688,3 +12655,459 @@ fn scanEnviron(t: *Threaded) void {
12688test {12655test {
12689 _ = @import("Threaded/test.zig");12656 _ = @import("Threaded/test.zig");
12690}12657}
12658
12659const use_parking_futex = switch (builtin.target.os.tag) {
12660 .windows => true, // RtlWaitOnAddress is a userland implementation anyway
12661 .netbsd => true, // NetBSD has `futex(2)`, but it's historically been quite buggy. TODO: evaluate whether it's okay to use now.
12662 .illumos => true, // Illumos has no futex mechanism
12663 else => false,
12664};
12665const use_parking_sleep = switch (builtin.target.os.tag) {
12666 // On Windows, we can implement sleep either with `NtDelayExecution` (which is how `SleepEx` in
12667 // kernel32 works) or `NtWaitForAlertByThreadId` (thread parking). We're already using the
12668 // latter for futex, so we may as well use it for sleeping too, to maximise code reuse. I'm
12669 // also more confident that it will always correctly handle the cancelation race (so "unpark"
12670 // before "park" causes "park" to return immediately): it *seems* like alertable sleeps paired
12671 // with `NtAlertThread` do actually do this too, but there could be some caveat (e.g. it might
12672 // fail under some specific condition), whereas `NtWaitForAlertByThreadId` must reliably trigger
12673 // this behavior because `RtlWaitOnAddress` relies on it.
12674 .windows => true,
12675
12676 // These targets have `_lwp_park`, which is superior to POSIX nanosleep because it has a better
12677 // cancelation mechanism.
12678 .netbsd,
12679 .illumos,
12680 => true,
12681
12682 else => false,
12683};
12684
12685const parking_futex = struct {
12686 comptime {
12687 assert(use_parking_futex);
12688 }
12689
12690 const Bucket = struct {
12691 /// Used as a fast check for `wake` to avoid having to acquire `mutex` to discover there are no
12692 /// waiters. It is important for `wait` to increment this *before* checking the futex value to
12693 /// avoid a race.
12694 num_waiters: std.atomic.Value(u32),
12695 /// Protects `waiters`.
12696 mutex: std.Thread.Mutex,
12697 waiters: std.DoublyLinkedList,
12698
12699 /// Prevent false sharing between buckets.
12700 _: void align(std.atomic.cache_line) = {},
12701
12702 const init: Bucket = .{ .num_waiters = .init(0), .mutex = .{}, .waiters = .{} };
12703 };
12704
12705 const Waiter = struct {
12706 node: std.DoublyLinkedList.Node,
12707 address: usize,
12708 tid: std.Thread.Id,
12709 /// `thread_status.cancelation` is `.parked` while the thread is waiting. The single thread
12710 /// which atomically updates it (to `.none` or `.canceling`) is responsible for:
12711 ///
12712 /// * Removing the `Waiter` from `Bucket.waiters`
12713 /// * Decrementing `Bucket.num_waiters`
12714 /// * Unparking the thread (*after* the above, so that the `Waiter` does not go out of scope
12715 /// while it is still in the `Bucket`).
12716 thread_status: *std.atomic.Value(Thread.Status),
12717 };
12718
12719 fn bucketForAddress(address: usize) *Bucket {
12720 const global = struct {
12721 /// Length must be a power of two. The longer this array, the less likely contention is
12722 /// between different futexes. This length seems like it'll provide a reasonable balance
12723 /// between contention and memory usage: assuming a 128-byte `Bucket` (due to cache line
12724 /// alignment), this uses 32 KiB of memory.
12725 var buckets: [256]Bucket = @splat(.init);
12726 };
12727
12728 // Here we use Fibonacci hashing: the golden ratio can be used to evenly redistribute input
12729 // values across a range, giving a poor, but extremely quick to compute, hash.
12730
12731 // This literal is the rounded value of '2^64 / phi' (where 'phi' is the golden ratio). The
12732 // shift then converts it to '2^b / phi', where 'b' is the pointer bit width.
12733 const fibonacci_multiplier = 0x9E3779B97F4A7C15 >> (64 - @bitSizeOf(usize));
12734 const hashed = address *% fibonacci_multiplier;
12735
12736 comptime assert(std.math.isPowerOfTwo(global.buckets.len));
12737 // The high bits of `hashed` have better entropy than the low bits.
12738 const index = hashed >> (@bitSizeOf(usize) - @ctz(global.buckets.len));
12739
12740 return &global.buckets[index];
12741 }
12742
12743 fn wait(ptr: *const u32, expect: u32, uncancelable: bool, timeout: Io.Timeout) Io.Cancelable!void {
12744 const bucket = bucketForAddress(@intFromPtr(ptr));
12745
12746 // Put the threadlocal access outside of the critical section.
12747 const opt_thread = Thread.current;
12748 const self_tid = if (opt_thread) |thread| thread.id else std.Thread.getCurrentId();
12749
12750 var waiter: Waiter = .{
12751 .node = undefined, // populated by list append
12752 .address = @intFromPtr(ptr),
12753 .tid = self_tid,
12754 .thread_status = undefined, // populated in critical section
12755 };
12756
12757 var status_buf: std.atomic.Value(Thread.Status) = undefined;
12758
12759 {
12760 bucket.mutex.lock();
12761 defer bucket.mutex.unlock();
12762
12763 _ = bucket.num_waiters.fetchAdd(1, .acquire);
12764
12765 if (@atomicLoad(u32, ptr, .monotonic) != expect) {
12766 assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0);
12767 return;
12768 }
12769
12770 // This is in the critical section to avoid marking the thread as parked until we're
12771 // certain that we're actually going to park.
12772 waiter.thread_status = status: {
12773 cancelable: {
12774 if (uncancelable) break :cancelable;
12775 const thread = opt_thread orelse break :cancelable;
12776 switch (thread.cancel_protection) {
12777 .blocked => break :cancelable,
12778 .unblocked => {},
12779 }
12780 thread.futex_waiter = &waiter;
12781 const old_status = thread.status.fetchOr(
12782 .{ .cancelation = @enumFromInt(0b001), .awaitable = .null },
12783 .release, // release `thread.futex_waiter`
12784 );
12785 switch (old_status.cancelation) {
12786 .none => {}, // status is now `.parked`
12787 .canceling => {
12788 // status is now `.canceled`
12789 assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0);
12790 return error.Canceled;
12791 },
12792 .canceled => break :cancelable, // status is still `.canceled`
12793 .parked => unreachable,
12794 .blocked => unreachable,
12795 .blocked_windows_dns => unreachable,
12796 .blocked_canceling => unreachable,
12797 }
12798 // We could now be unparked for a cancelation at any time!
12799 break :status &thread.status;
12800 }
12801 // This is an uncancelable wait, so just use `status_buf`. Note that the value of
12802 // `status_buf.awaitable` is irrelevant because this is only visible to futex code,
12803 // while only cancelation cares about `awaitable`.
12804 status_buf.raw = .{ .cancelation = .parked, .awaitable = .null };
12805 break :status &status_buf;
12806 };
12807
12808 bucket.waiters.append(&waiter.node);
12809 }
12810
12811 if (park(timeout, ptr)) {
12812 // We were unparked by either `wake` or cancelation, so our current status is either
12813 // `.none` or `.canceling`. In either case, they've already removed `waiter` from
12814 // `bucket`, so we have nothing more to do!
12815 } else |err| switch (err) {
12816 error.Timeout => {
12817 // We're not out of the woods yet: an unpark could race with the timeout.
12818 const old_status = waiter.thread_status.fetchAnd(
12819 .{ .cancelation = @enumFromInt(0b110), .awaitable = .all_ones },
12820 .monotonic,
12821 );
12822 switch (old_status.cancelation) {
12823 .parked => {
12824 // No race. It is our responsibility to remove `waiter` from `bucket`.
12825 // New status is `.none`.
12826 bucket.mutex.lock();
12827 defer bucket.mutex.unlock();
12828 bucket.waiters.remove(&waiter.node);
12829 assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0);
12830 },
12831 .none, .canceling => {
12832 // Race condition: the timeout was reached, then `wake` or a canceler tried
12833 // to unpark us. Whoever did that will remove us from `bucket`. Wait for
12834 // that (and drop the unpark request in doing so).
12835 // New status is `.none` or `.canceling` respectively.
12836 park(.none, ptr) catch |e| switch (e) {
12837 error.Timeout => unreachable,
12838 };
12839 },
12840 .canceled => unreachable,
12841 .blocked => unreachable,
12842 .blocked_windows_dns => unreachable,
12843 .blocked_canceling => unreachable,
12844 }
12845 },
12846 }
12847 }
12848
12849 fn wake(ptr: *const u32, max_waiters: u32) void {
12850 if (max_waiters == 0) return;
12851
12852 const bucket = bucketForAddress(@intFromPtr(ptr));
12853
12854 // To ensure the store to `ptr` is ordered before this check, we effectively want a `.release`
12855 // load, but that doesn't exist in the C11 memory model, so emulate it with a non-mutating rmw.
12856 if (bucket.num_waiters.fetchAdd(0, .release) == 0) {
12857 @branchHint(.likely);
12858 return; // no waiters
12859 }
12860
12861 // Waiters removed from the linked list under the mutex so we can unpark their threads outside
12862 // of the critical section. This forms a singly-linked list of waiters using `Waiter.node.next`.
12863 var waking_head: ?*std.DoublyLinkedList.Node = null;
12864 {
12865 bucket.mutex.lock();
12866 defer bucket.mutex.unlock();
12867
12868 var num_removed: u32 = 0;
12869 var it = bucket.waiters.first;
12870 while (num_removed < max_waiters) {
12871 const waiter: *Waiter = @fieldParentPtr("node", it orelse break);
12872 it = waiter.node.next;
12873 if (waiter.address != @intFromPtr(ptr)) continue;
12874 const old_status = waiter.thread_status.fetchAnd(
12875 .{ .cancelation = @enumFromInt(0b110), .awaitable = .all_ones },
12876 .monotonic,
12877 );
12878 switch (old_status.cancelation) {
12879 .parked => {}, // state updated to `.none`
12880 .none => unreachable, // if another `wake` call is unparking this thread, it should have removed it from the list
12881 .canceling => continue, // race with a canceler who hasn't called `removeCanceledWaiter` yet
12882 .canceled => unreachable,
12883 .blocked => unreachable,
12884 .blocked_windows_dns => unreachable,
12885 .blocked_canceling => unreachable,
12886 }
12887 // We're waking this waiter. Remove them from the bucket and add them to our local list.
12888 bucket.waiters.remove(&waiter.node);
12889 waiter.node.next = waking_head;
12890 waking_head = &waiter.node;
12891 num_removed += 1;
12892 // Signal to `waiter` that they're about to be unparked, in case we're racing with their
12893 // timeout. See corresponding logic in `wake`.
12894 waiter.address = 0;
12895 }
12896
12897 _ = bucket.num_waiters.fetchSub(num_removed, .monotonic);
12898 }
12899
12900 var unpark_buf: [128]UnparkTid = undefined;
12901 var unpark_len: usize = 0;
12902
12903 // Finally, unpark the threads.
12904 while (waking_head) |node| {
12905 waking_head = node.next;
12906 const waiter: *Waiter = @fieldParentPtr("node", node);
12907 unpark_buf[unpark_len] = waiter.tid;
12908 unpark_len += 1;
12909 if (unpark_len == unpark_buf.len) {
12910 unpark(&unpark_buf, ptr);
12911 unpark_len = 0;
12912 }
12913 }
12914 if (unpark_len > 0) {
12915 unpark(unpark_buf[0..unpark_len], ptr);
12916 }
12917 }
12918
12919 fn removeCanceledWaiter(waiter: *Waiter) void {
12920 const bucket = bucketForAddress(waiter.address);
12921 bucket.mutex.lock();
12922 defer bucket.mutex.unlock();
12923 bucket.waiters.remove(&waiter.node);
12924 assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0);
12925 }
12926};
12927const parking_sleep = struct {
12928 comptime {
12929 assert(use_parking_sleep);
12930 }
12931 fn sleep(timeout: Io.Timeout) Io.Cancelable!void {
12932 const opt_thread = Thread.current;
12933 cancelable: {
12934 const thread = opt_thread orelse break :cancelable;
12935 switch (thread.cancel_protection) {
12936 .blocked => break :cancelable,
12937 .unblocked => {},
12938 }
12939 thread.futex_waiter = null;
12940 {
12941 const old_status = thread.status.fetchOr(
12942 .{ .cancelation = @enumFromInt(0b001), .awaitable = .null },
12943 .release, // release `thread.futex_waiter`
12944 );
12945 switch (old_status.cancelation) {
12946 .none => {}, // status is now `.parked`
12947 .canceling => return error.Canceled, // status is now `.canceled`
12948 .canceled => break :cancelable, // status is still `.canceled`
12949 .parked => unreachable,
12950 .blocked => unreachable,
12951 .blocked_windows_dns => unreachable,
12952 .blocked_canceling => unreachable,
12953 }
12954 }
12955 if (park(timeout, null)) {
12956 // The only reason this could possibly happen is cancelation.
12957 const old_status = thread.status.load(.monotonic);
12958 assert(old_status.cancelation == .canceling);
12959 thread.status.store(
12960 .{ .cancelation = .canceled, .awaitable = old_status.awaitable },
12961 .monotonic,
12962 );
12963 return error.Canceled;
12964 } else |err| switch (err) {
12965 error.Timeout => {
12966 // We're not out of the woods yet: an unpark could race with the timeout.
12967 const old_status = thread.status.fetchAnd(
12968 .{ .cancelation = @enumFromInt(0b110), .awaitable = .all_ones },
12969 .monotonic,
12970 );
12971 switch (old_status.cancelation) {
12972 .parked => return, // No race; new status is `.none`
12973 .canceling => {
12974 // Race condition: the timeout was reached, then someone tried to unpark
12975 // us for a cancelation. Whoever did that will have called `unpark`, so
12976 // drop that unpark request by waiting for it.
12977 // Status is still `.canceling`.
12978 park(.none, null) catch |e| switch (e) {
12979 error.Timeout => unreachable,
12980 };
12981 return;
12982 },
12983 .none => unreachable,
12984 .canceled => unreachable,
12985 .blocked => unreachable,
12986 .blocked_windows_dns => unreachable,
12987 .blocked_canceling => unreachable,
12988 }
12989 },
12990 }
12991 }
12992 // Uncancelable sleep; we expect not to be manually unparked.
12993 if (park(timeout, null)) {
12994 unreachable; // unexpected unpark
12995 } else |err| switch (err) {
12996 error.Timeout => return,
12997 }
12998 }
12999};
13000
13001/// `addr_hint` has no semantic effect, but may allow the OS to optimize this operation.
13002fn park(timeout: Io.Timeout, addr_hint: ?*const anyopaque) error{Timeout}!void {
13003 comptime assert(use_parking_futex or use_parking_sleep);
13004 switch (builtin.target.os.tag) {
13005 .windows => {
13006 var timeout_buf: windows.LARGE_INTEGER = undefined;
13007 const raw_timeout: ?*windows.LARGE_INTEGER = timeout: switch (timeout) {
13008 .none => null,
13009 .deadline => |timestamp| continue :timeout .{ .duration = .{
13010 .clock = timestamp.clock,
13011 .raw = (nowWindows(timestamp.clock) catch unreachable).durationTo(timestamp.raw),
13012 } },
13013 .duration => |duration| {
13014 _ = duration.clock; // Windows only supports monotonic
13015 timeout_buf = @intCast(@divTrunc(-duration.raw.nanoseconds, 100));
13016 break :timeout &timeout_buf;
13017 },
13018 };
13019 // `RtlWaitOnAddress` passes the futex address in as the first argument to this call,
13020 // but it's unclear what that actually does, especially since `NtAlertThreadByThreadId`
13021 // does *not* accept the address so the kernel can't really be using it as a hint. An
13022 // old Microsoft blog post discusses a more traditional futex-like mechanism in the
13023 // kernel which definitely isn't how `RtlWaitOnAddress` works today:
13024 //
13025 // https://devblogs.microsoft.com/oldnewthing/20160826-00/?p=94185
13026 //
13027 // ...so it's possible this argument is simply a remnant which no longer does anything
13028 // (perhaps the implementation changed during development but someone forgot to remove
13029 // this parameter). However, to err on the side of caution, let's match the behavior of
13030 // `RtlWaitOnAddress` and pass the pointer, in case the kernel ever does something
13031 // stupid such as trying to dereference it.
13032 switch (windows.ntdll.NtWaitForAlertByThreadId(addr_hint, raw_timeout)) {
13033 .ALERTED => return,
13034 .TIMEOUT => return error.Timeout,
13035 else => unreachable,
13036 }
13037 },
13038 .netbsd => {
13039 var ts_buf: posix.timespec = undefined;
13040 const ts: ?*posix.timespec, const abstime: bool, const clock_real: bool = switch (timeout) {
13041 .none => .{ null, false, false },
13042 .deadline => |timestamp| timeout: {
13043 ts_buf = timestampToPosix(timestamp.raw.nanoseconds);
13044 break :timeout .{ &ts_buf, true, timestamp.clock == .real };
13045 },
13046 .duration => |duration| timeout: {
13047 ts_buf = timestampToPosix(duration.raw.nanoseconds);
13048 break :timeout .{ &ts_buf, false, duration.clock == .real };
13049 },
13050 };
13051 switch (posix.errno(std.c._lwp_park(
13052 if (clock_real) .REALTIME else .MONOTONIC,
13053 .{ .ABSTIME = abstime },
13054 ts,
13055 0,
13056 addr_hint,
13057 null,
13058 ))) {
13059 .SUCCESS, .ALREADY, .INTR => return,
13060 .TIMEDOUT => return error.Timeout,
13061 .INVAL => unreachable,
13062 .SRCH => unreachable,
13063 else => unreachable,
13064 }
13065 },
13066 .illumos => @panic("TODO: illumos lwp_park"),
13067 else => comptime unreachable,
13068 }
13069}
13070
13071const UnparkTid = switch (builtin.target.os.tag) {
13072 // `NtAlertMultipleThreadByThreadId` is weird and wants 64-bit thread handles?
13073 .windows => usize,
13074 else => std.Thread.Id,
13075};
13076/// `addr_hint` has no semantic effect, but may allow the OS to optimize this operation.
13077fn unpark(tids: []const UnparkTid, addr_hint: ?*const anyopaque) void {
13078 comptime assert(use_parking_futex or use_parking_sleep);
13079 switch (builtin.target.os.tag) {
13080 .windows => {
13081 // TODO: this condition is currently disabled because mingw-w64 does not contain this
13082 // symbol. Once it's added, enable this check to use the new bulk API where possible.
13083 if (false and (builtin.os.version_range.windows.isAtLeast(.win11_dt) orelse false)) {
13084 _ = windows.ntdll.NtAlertMultipleThreadByThreadId(tids.ptr, @intCast(tids.len), null, null);
13085 } else {
13086 for (tids) |tid| {
13087 _ = windows.ntdll.NtAlertThreadByThreadId(@intCast(tid));
13088 }
13089 }
13090 },
13091 .netbsd => {
13092 switch (posix.errno(std.c._lwp_unpark_all(@ptrCast(tids.ptr), tids.len, addr_hint))) {
13093 .SUCCESS => return,
13094 // For errors, fall through to a loop over `tids`, though this is only expected to
13095 // be possible for ENOMEM (and even that is questionable).
13096 .SRCH => recoverableOsBugDetected(),
13097 .FAULT => recoverableOsBugDetected(),
13098 .INVAL => recoverableOsBugDetected(),
13099 .NOMEM => {},
13100 else => recoverableOsBugDetected(),
13101 }
13102 for (tids) |tid| {
13103 switch (posix.errno(std.c._lwp_unpark(@bitCast(tid), addr_hint))) {
13104 .SUCCESS => {},
13105 .SRCH => recoverableOsBugDetected(),
13106 else => recoverableOsBugDetected(),
13107 }
13108 }
13109 },
13110 .illumos => @panic("TODO: illumos lwp_unpark"),
13111 else => comptime unreachable,
13112 }
13113}
lib/std/c.zig+3
...@@ -11399,6 +11399,9 @@ pub const vm_region_flavor_t = darwin.vm_region_flavor_t;...@@ -11399,6 +11399,9 @@ pub const vm_region_flavor_t = darwin.vm_region_flavor_t;
1139911399
11400pub const _ksiginfo = netbsd._ksiginfo;11400pub const _ksiginfo = netbsd._ksiginfo;
11401pub const _lwp_self = netbsd._lwp_self;11401pub const _lwp_self = netbsd._lwp_self;
11402pub const _lwp_park = netbsd._lwp_park;
11403pub const _lwp_unpark = netbsd._lwp_unpark;
11404pub const _lwp_unpark_all = netbsd._lwp_unpark_all;
11402pub const lwpid_t = netbsd.lwpid_t;11405pub const lwpid_t = netbsd.lwpid_t;
1140311406
11404pub const lwp_gettid = dragonfly.lwp_gettid;11407pub const lwp_gettid = dragonfly.lwp_gettid;
lib/std/c/netbsd.zig+19-1
...@@ -1,17 +1,35 @@...@@ -1,17 +1,35 @@
1const std = @import("../std.zig");1const std = @import("../std.zig");
2const clock_t = std.c.clock_t;2const clock_t = std.c.clock_t;
3const clockid_t = std.c.clockid_t;
3const pid_t = std.c.pid_t;4const pid_t = std.c.pid_t;
4const pthread_t = std.c.pthread_t;5const pthread_t = std.c.pthread_t;
5const sigval_t = std.c.sigval_t;6const sigval_t = std.c.sigval_t;
6const uid_t = std.c.uid_t;7const uid_t = std.c.uid_t;
8const timespec = std.c.timespec;
79
8pub extern "c" fn ptrace(request: c_int, pid: pid_t, addr: ?*anyopaque, data: c_int) c_int;10pub extern "c" fn ptrace(request: c_int, pid: pid_t, addr: ?*anyopaque, data: c_int) c_int;
911
10pub const lwpid_t = i32;12pub const lwpid_t = i32;
1113
12pub extern "c" fn _lwp_self() lwpid_t;
13pub extern "c" fn pthread_setname_np(thread: pthread_t, name: [*:0]const u8, arg: ?*anyopaque) c_int;14pub extern "c" fn pthread_setname_np(thread: pthread_t, name: [*:0]const u8, arg: ?*anyopaque) c_int;
1415
16pub extern "c" fn _lwp_self() lwpid_t;
17
18pub extern "c" fn _lwp_park(
19 clock_id: clockid_t,
20 flags: packed struct(u32) {
21 ABSTIME: bool = false,
22 unused: u31 = 0,
23 },
24 ts: ?*timespec,
25 unpark: lwpid_t,
26 hint: ?*const anyopaque,
27 unpark_hint: ?*const anyopaque,
28) c_int;
29
30pub extern "c" fn _lwp_unpark(lwp: lwpid_t, hint: ?*const anyopaque) c_int;
31pub extern "c" fn _lwp_unpark_all(targets: [*]const lwpid_t, ntargets: usize, hint: ?*const anyopaque) c_int;
32
15pub const TCIFLUSH = 1;33pub const TCIFLUSH = 1;
16pub const TCOFLUSH = 2;34pub const TCOFLUSH = 2;
17pub const TCIOFLUSH = 3;35pub const TCIOFLUSH = 3;
lib/std/debug/SelfInfo/Windows.zig+1-2
...@@ -315,8 +315,7 @@ const Module = struct {...@@ -315,8 +315,7 @@ const Module = struct {
315 );315 );
316 if (len == 0) return error.MissingDebugInfo;316 if (len == 0) return error.MissingDebugInfo;
317 const name_w = name_buffer[0 .. len + 4 :0];317 const name_w = name_buffer[0 .. len + 4 :0];
318 // TODO eliminate the reference to Io.Threaded.global_single_threaded here318 const coff_file = Io.Threaded.dirOpenFileWtf16(null, name_w, .{}) catch |err| switch (err) {
319 const coff_file = Io.Threaded.global_single_threaded.dirOpenFileWtf16(null, name_w, .{}) catch |err| switch (err) {
320 error.Canceled => |e| return e,319 error.Canceled => |e| return e,
321 error.Unexpected => |e| return e,320 error.Unexpected => |e| return e,
322 error.FileNotFound => return error.MissingDebugInfo,321 error.FileNotFound => return error.MissingDebugInfo,
lib/std/os/windows.zig+6-66
...@@ -2253,7 +2253,7 @@ pub fn GetProcessHeap() ?*HEAP {...@@ -2253,7 +2253,7 @@ pub fn GetProcessHeap() ?*HEAP {
2253pub const OBJECT_ATTRIBUTES = extern struct {2253pub const OBJECT_ATTRIBUTES = extern struct {
2254 Length: ULONG,2254 Length: ULONG,
2255 RootDirectory: ?HANDLE,2255 RootDirectory: ?HANDLE,
2256 ObjectName: *UNICODE_STRING,2256 ObjectName: ?*UNICODE_STRING,
2257 Attributes: ATTRIBUTES,2257 Attributes: ATTRIBUTES,
2258 SecurityDescriptor: ?*anyopaque,2258 SecurityDescriptor: ?*anyopaque,
2259 SecurityQualityOfService: ?*anyopaque,2259 SecurityQualityOfService: ?*anyopaque,
...@@ -2306,6 +2306,7 @@ pub const OpenError = error{...@@ -2306,6 +2306,7 @@ pub const OpenError = error{
2306 NetworkNotFound,2306 NetworkNotFound,
2307 AntivirusInterference,2307 AntivirusInterference,
2308 BadPathName,2308 BadPathName,
2309 OperationCanceled,
2309};2310};
23102311
2311pub const OpenFileOptions = struct {2312pub const OpenFileOptions = struct {
...@@ -2405,6 +2406,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN...@@ -2405,6 +2406,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
2405 continue;2406 continue;
2406 },2407 },
2407 .VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference,2408 .VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference,
2409 .CANCELLED => return error.OperationCanceled,
2408 else => return unexpectedStatus(rc),2410 else => return unexpectedStatus(rc),
2409 }2411 }
2410 }2412 }
...@@ -2985,6 +2987,7 @@ pub const ReadLinkError = error{...@@ -2985,6 +2987,7 @@ pub const ReadLinkError = error{
2985 AntivirusInterference,2987 AntivirusInterference,
2986 UnsupportedReparsePointType,2988 UnsupportedReparsePointType,
2987 NotLink,2989 NotLink,
2990 OperationCanceled,
2988};2991};
29892992
2990/// `sub_path_w` will never be accessed after `out_buffer` has been written to, so it2993/// `sub_path_w` will never be accessed after `out_buffer` has been written to, so it
...@@ -3015,6 +3018,7 @@ pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u16) ReadLi...@@ -3015,6 +3018,7 @@ pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u16) ReadLi
3015 const rc = DeviceIoControl(result_handle, FSCTL.GET_REPARSE_POINT, .{ .out = reparse_buf[0..] });3018 const rc = DeviceIoControl(result_handle, FSCTL.GET_REPARSE_POINT, .{ .out = reparse_buf[0..] });
3016 switch (rc) {3019 switch (rc) {
3017 .SUCCESS => {},3020 .SUCCESS => {},
3021 .CANCELLED => return error.OperationCanceled,
3018 .NOT_A_REPARSE_POINT => return error.NotLink,3022 .NOT_A_REPARSE_POINT => return error.NotLink,
3019 else => return unexpectedStatus(rc),3023 else => return unexpectedStatus(rc),
3020 }3024 }
...@@ -3339,71 +3343,6 @@ pub fn GetStdHandle(handle_id: DWORD) GetStdHandleError!HANDLE {...@@ -3339,71 +3343,6 @@ pub fn GetStdHandle(handle_id: DWORD) GetStdHandleError!HANDLE {
3339 return handle;3343 return handle;
3340}3344}
33413345
3342pub const SetFilePointerError = error{
3343 Unseekable,
3344 Unexpected,
3345};
3346
3347/// The SetFilePointerEx function with the `dwMoveMethod` parameter set to `FILE_BEGIN`.
3348pub fn SetFilePointerEx_BEGIN(handle: HANDLE, offset: u64) SetFilePointerError!void {
3349 // "The starting point is zero or the beginning of the file. If [FILE_BEGIN]
3350 // is specified, then the liDistanceToMove parameter is interpreted as an unsigned value."
3351 // https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-setfilepointerex
3352 const ipos = @as(LARGE_INTEGER, @bitCast(offset));
3353 if (kernel32.SetFilePointerEx(handle, ipos, null, FILE_BEGIN) == 0) {
3354 switch (GetLastError()) {
3355 .INVALID_FUNCTION => return error.Unseekable,
3356 .NEGATIVE_SEEK => return error.Unseekable,
3357 .INVALID_PARAMETER => unreachable,
3358 .INVALID_HANDLE => unreachable,
3359 else => |err| return unexpectedError(err),
3360 }
3361 }
3362}
3363
3364/// The SetFilePointerEx function with the `dwMoveMethod` parameter set to `FILE_CURRENT`.
3365pub fn SetFilePointerEx_CURRENT(handle: HANDLE, offset: i64) SetFilePointerError!void {
3366 if (kernel32.SetFilePointerEx(handle, offset, null, FILE_CURRENT) == 0) {
3367 switch (GetLastError()) {
3368 .INVALID_FUNCTION => return error.Unseekable,
3369 .NEGATIVE_SEEK => return error.Unseekable,
3370 .INVALID_PARAMETER => unreachable,
3371 .INVALID_HANDLE => unreachable,
3372 else => |err| return unexpectedError(err),
3373 }
3374 }
3375}
3376
3377/// The SetFilePointerEx function with the `dwMoveMethod` parameter set to `FILE_END`.
3378pub fn SetFilePointerEx_END(handle: HANDLE, offset: i64) SetFilePointerError!void {
3379 if (kernel32.SetFilePointerEx(handle, offset, null, FILE_END) == 0) {
3380 switch (GetLastError()) {
3381 .INVALID_FUNCTION => return error.Unseekable,
3382 .NEGATIVE_SEEK => return error.Unseekable,
3383 .INVALID_PARAMETER => unreachable,
3384 .INVALID_HANDLE => unreachable,
3385 else => |err| return unexpectedError(err),
3386 }
3387 }
3388}
3389
3390/// The SetFilePointerEx function with parameters to get the current offset.
3391pub fn SetFilePointerEx_CURRENT_get(handle: HANDLE) SetFilePointerError!u64 {
3392 var result: LARGE_INTEGER = undefined;
3393 if (kernel32.SetFilePointerEx(handle, 0, &result, FILE_CURRENT) == 0) {
3394 switch (GetLastError()) {
3395 .INVALID_FUNCTION => return error.Unseekable,
3396 .NEGATIVE_SEEK => return error.Unseekable,
3397 .INVALID_PARAMETER => unreachable,
3398 .INVALID_HANDLE => unreachable,
3399 else => |err| return unexpectedError(err),
3400 }
3401 }
3402 // Based on the docs for FILE_BEGIN, it seems that the returned signed integer
3403 // should be interpreted as an unsigned integer.
3404 return @as(u64, @bitCast(result));
3405}
3406
3407pub const QueryObjectNameError = error{3346pub const QueryObjectNameError = error{
3408 AccessDenied,3347 AccessDenied,
3409 InvalidHandle,3348 InvalidHandle,
...@@ -3562,6 +3501,7 @@ pub fn GetFinalPathNameByHandle(...@@ -3562,6 +3501,7 @@ pub fn GetFinalPathNameByHandle(
3562 error.NetworkNotFound => return error.Unexpected,3501 error.NetworkNotFound => return error.Unexpected,
3563 error.AntivirusInterference => return error.Unexpected,3502 error.AntivirusInterference => return error.Unexpected,
3564 error.BadPathName => return error.Unexpected,3503 error.BadPathName => return error.Unexpected,
3504 error.OperationCanceled => @panic("TODO: better integrate cancelation"),
3565 else => |e| return e,3505 else => |e| return e,
3566 };3506 };
3567 defer CloseHandle(mgmt_handle);3507 defer CloseHandle(mgmt_handle);
lib/std/os/windows/ntdll.zig+27
...@@ -554,3 +554,30 @@ pub extern "ntdll" fn RtlWakeConditionVariable(...@@ -554,3 +554,30 @@ pub extern "ntdll" fn RtlWakeConditionVariable(
554pub extern "ntdll" fn RtlWakeAllConditionVariable(554pub extern "ntdll" fn RtlWakeAllConditionVariable(
555 ConditionVariable: *CONDITION_VARIABLE,555 ConditionVariable: *CONDITION_VARIABLE,
556) callconv(.winapi) void;556) callconv(.winapi) void;
557
558pub extern "ntdll" fn NtWaitForAlertByThreadId(
559 Address: ?*const anyopaque,
560 Timeout: ?*const LARGE_INTEGER,
561) callconv(.winapi) NTSTATUS;
562pub extern "ntdll" fn NtAlertThreadByThreadId(
563 ThreadId: DWORD,
564) callconv(.winapi) NTSTATUS;
565pub extern "ntdll" fn NtAlertMultipleThreadByThreadId(
566 ThreadIds: [*]const ULONG_PTR,
567 ThreadCount: ULONG,
568 Unknown1: ?*const anyopaque,
569 Unknown2: ?*const anyopaque,
570) callconv(.winapi) NTSTATUS;
571
572pub extern "ntdll" fn NtOpenThread(
573 ThreadHandle: *HANDLE,
574 DesiredAccess: ACCESS_MASK,
575 ObjectAttributes: *const OBJECT_ATTRIBUTES,
576 ClientId: *const windows.CLIENT_ID,
577) callconv(.winapi) NTSTATUS;
578
579pub extern "ntdll" fn NtCancelSynchronousIoFile(
580 ThreadHandle: HANDLE,
581 RequestToCancel: ?*IO_STATUS_BLOCK,
582 IoStatusBlock: *IO_STATUS_BLOCK,
583) callconv(.winapi) NTSTATUS;
lib/std/posix.zig+1
...@@ -1124,6 +1124,7 @@ pub fn mkdirW(dir_path_w: []const u16, mode: mode_t) MakeDirError!void {...@@ -1124,6 +1124,7 @@ pub fn mkdirW(dir_path_w: []const u16, mode: mode_t) MakeDirError!void {
1124 error.NoDevice => return error.Unexpected,1124 error.NoDevice => return error.Unexpected,
1125 error.WouldBlock => return error.Unexpected,1125 error.WouldBlock => return error.Unexpected,
1126 error.AntivirusInterference => return error.Unexpected,1126 error.AntivirusInterference => return error.Unexpected,
1127 error.OperationCanceled => return error.Unexpected,
1127 else => |e| return e,1128 else => |e| return e,
1128 };1129 };
1129 windows.CloseHandle(sub_dir_handle);1130 windows.CloseHandle(sub_dir_handle);
lib/std/process/Child.zig+2-2
...@@ -778,6 +778,7 @@ fn spawnWindows(self: *Child, io: Io) SpawnError!void {...@@ -778,6 +778,7 @@ fn spawnWindows(self: *Child, io: Io) SpawnError!void {
778 error.WouldBlock => return error.Unexpected, // not possible for "NUL"778 error.WouldBlock => return error.Unexpected, // not possible for "NUL"
779 error.NetworkNotFound => return error.Unexpected, // not possible for "NUL"779 error.NetworkNotFound => return error.Unexpected, // not possible for "NUL"
780 error.AntivirusInterference => return error.Unexpected, // not possible for "NUL"780 error.AntivirusInterference => return error.Unexpected, // not possible for "NUL"
781 error.OperationCanceled => return error.Unexpected, // we're not canceling the operation
781 else => |e| return e,782 else => |e| return e,
782 }783 }
783 else784 else
...@@ -1129,8 +1130,7 @@ fn windowsCreateProcessPathExt(...@@ -1129,8 +1130,7 @@ fn windowsCreateProcessPathExt(
1129 defer dir_buf.shrinkRetainingCapacity(dir_path_len);1130 defer dir_buf.shrinkRetainingCapacity(dir_path_len);
1130 const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0];1131 const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
1131 const prefixed_path = try windows.wToPrefixedFileW(null, dir_path_z);1132 const prefixed_path = try windows.wToPrefixedFileW(null, dir_path_z);
1132 // TODO eliminate this reference1133 break :dir Io.Threaded.dirOpenDirWindows(.cwd(), prefixed_path.span(), .{
1133 break :dir Io.Threaded.global_single_threaded.dirOpenDirWindows(.cwd(), prefixed_path.span(), .{
1134 .iterate = true,1134 .iterate = true,
1135 }) catch return error.FileNotFound;1135 }) catch return error.FileNotFound;
1136 };1136 };