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 {
105105 };
106106};
107107
108pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum {
109 enabled,
110 disabled,
111} else enum {
112 disabled,
113};
108pub const RobustCancel = enum { enabled, disabled };
114109
115110pub const Pid = if (native_os == .linux) enum(posix.pid_t) {
116111 unknown = 0,
......@@ -514,13 +509,21 @@ const AwaitableId = enum(@Int(.unsigned, @bitSizeOf(usize) - 3)) {
514509
515510const Thread = struct {
516511 next: ?*Thread,
517 /// The value that needs to be passed to pthread_kill or tgkill in order to
518 /// send a signal.
519 signalee_id: SignaleeId,
512
513 id: std.Thread.Id,
514 handle: Handle,
520515
521516 status: std.atomic.Value(Status),
522517
523518 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
525528 const Status = packed struct(usize) {
526529 /// The specific values of these enum fields are chosen to simplify the implementation of
......@@ -531,7 +534,7 @@ const Thread = struct {
531534 none = 0b000,
532535
533536 /// 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`.
535538 /// To request cancelation, set the status to `.canceling` and unpark the thread.
536539 /// To unpark for another reason (futex wake), set the status to `.none` and unpark the thread.
537540 parked = 0b001,
......@@ -540,8 +543,8 @@ const Thread = struct {
540543 /// To request cancelation, set the status to `.blocked_canceling` and repeatedly interrupt the system call until the status changes.
541544 blocked = 0b011,
542545
543 /// Windows-only: the thread is blocked on a DNS query.
544 /// To request cancelation, set the status to `.canceling` and call `DnsCancelQuery`.
546 /// Windows-only: the thread is blocked in a call to `GetAddrInfoExW`.
547 /// To request cancelation, set the status to `.canceling` and call `GetAddrInfoExCancel`.
545548 blocked_windows_dns = 0b010,
546549
547550 /// The thread has an outstanding cancelation request but is not in a cancelable operation.
......@@ -597,10 +600,6 @@ const Thread = struct {
597600 }
598601 }
599602
600 fn currentSignaleeId() SignaleeId {
601 return if (std.Thread.use_pthreads) std.c.pthread_self() else std.Thread.getCurrentId();
602 }
603
604603 fn futexWaitUncancelable(ptr: *const u32, expect: u32, timeout_ns: ?u64) void {
605604 return Thread.futexWaitInner(ptr, expect, true, timeout_ns) catch unreachable;
606605 }
......@@ -614,8 +613,19 @@ const Thread = struct {
614613
615614 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()) {
618627 comptime assert(builtin.cpu.has(.wasm, .atomics));
628 // TODO implement cancelation for WASM futex waits by signaling the futex
619629 if (!uncancelable) try Thread.checkCancel();
620630 const to: i64 = if (timeout_ns) |ns| ns else -1;
621631 const signed_expect: i32 = @bitCast(expect);
......@@ -689,24 +699,6 @@ const Thread = struct {
689699 else => recoverableOsBugDetected(),
690700 }
691701 },
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 },
710702 .freebsd => {
711703 const flags = @intFromEnum(std.c.UMTX_OP.WAIT_UINT_PRIVATE);
712704 var tm_size: usize = 0;
......@@ -738,7 +730,7 @@ const Thread = struct {
738730 tm_ptr = &tm;
739731 tm = timestampToPosix(ns);
740732 }
741 if (thread) |t| try t.beginSyscall();
733 const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start();
742734 const rc = std.c.futex(
743735 ptr,
744736 std.c.FUTEX.WAIT | std.c.FUTEX.PRIVATE_FLAG,
......@@ -746,7 +738,7 @@ const Thread = struct {
746738 tm_ptr,
747739 null, // uaddr2 is ignored
748740 );
749 if (thread) |t| t.endSyscall();
741 syscall.finish();
750742 if (is_debug) switch (posix.errno(rc)) {
751743 .SUCCESS => {},
752744 .NOSYS => unreachable, // constant op known good value
......@@ -765,9 +757,9 @@ const Thread = struct {
765757 } else {
766758 timeout_us = 0;
767759 }
768 if (thread) |t| try t.beginSyscall();
760 const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start();
769761 const rc = std.c.umtx_sleep(@ptrCast(ptr), @bitCast(expect), timeout_us);
770 if (thread) |t| t.endSyscall();
762 syscall.finish();
771763 if (is_debug) switch (std.posix.errno(rc)) {
772764 .SUCCESS => {},
773765 .BUSY => {}, // ptr != expect
......@@ -777,14 +769,7 @@ const Thread = struct {
777769 else => unreachable,
778770 };
779771 },
780 else => if (std.Thread.use_pthreads) {
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 },
772 else => @compileError("unimplemented: futexWait"),
788773 }
789774 }
790775
......@@ -794,7 +779,9 @@ const Thread = struct {
794779
795780 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()) {
798785 comptime assert(builtin.cpu.has(.wasm, .atomics));
799786 const woken_count = asm volatile (
800787 \\local.get %[ptr]
......@@ -839,12 +826,6 @@ const Thread = struct {
839826 }
840827 }
841828 },
842 .windows => {
843 switch (max_waiters) {
844 1 => windows.ntdll.RtlWakeAddressSingle(ptr),
845 else => windows.ntdll.RtlWakeAddressAll(ptr),
846 }
847 },
848829 .freebsd => {
849830 const rc = std.c._umtx_op(
850831 @intFromPtr(ptr),
......@@ -877,11 +858,7 @@ const Thread = struct {
877858 @min(max_waiters, std.math.maxInt(c_int)),
878859 );
879860 },
880 else => if (std.Thread.use_pthreads) {
881 return pthreads_futex.wake(ptr, max_waiters);
882 } else {
883 @compileError("unimplemented: futexWake");
884 },
861 else => @compileError("unimplemented: futexWake"),
885862 }
886863 }
887864
......@@ -905,10 +882,14 @@ const Thread = struct {
905882 .parked => thread.status.cmpxchgWeak(
906883 .{ .cancelation = .parked, .awaitable = awaitable },
907884 .{ .cancelation = .canceling, .awaitable = awaitable },
908 .monotonic,
885 .acquire, // acquire `thread.futex_waiter`
909886 .monotonic,
910887 ) 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);
912893 return false;
913894 },
914895
......@@ -924,7 +905,15 @@ const Thread = struct {
924905 .{ .cancelation = .canceling, .awaitable = awaitable },
925906 .monotonic,
926907 .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
929918 .canceling, .canceled => {
930919 // This can happen when the task start raced with the cancelation, so the thread
......@@ -951,26 +940,38 @@ const Thread = struct {
951940 const bad_status: Status = .{ .cancelation = .blocked_canceling, .awaitable = awaitable };
952941 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 the
955 // store that made `thread` available to us.
956 const signalee_id = thread.signalee_id;
943 // The thread ID and/or handle can be read non-atomically because they never change and were
944 // released by the store that made `thread` available to us.
957945
958946 if (std.Thread.use_pthreads) {
959 if (std.c.pthread_kill(signalee_id, .IO) != 0) return false;
960 } else if (native_os == .linux) {
961 const pid: posix.pid_t = pid: {
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;
947 return switch (std.c.pthread_kill(thread.handle, .IO)) {
948 0 => true,
949 else => false,
967950 };
968 if (std.os.linux.tgkill(pid, @bitCast(signalee_id), .IO) != 0) return false;
969 } else {
970 @compileError("MLUGG TODO");
951 } else switch (builtin.target.os.tag) {
952 .linux => {
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,
971974 }
972
973 return true;
974975 }
975976
976977 /// 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 {
10691070 s.finish();
10701071 return posix.unexpectedErrno(err);
10711072 }
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 }
10721085};
10731086
10741087const max_iovecs_len = 8;
......@@ -1233,15 +1246,45 @@ fn join(t: *Threaded) void {
12331246fn worker(t: *Threaded) void {
12341247 var thread: Thread = .{
12351248 .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 },
12371254 .status = .init(.{
12381255 .cancelation = .none,
12391256 .awaitable = .null,
12401257 }),
12411258 .cancel_protection = .unblocked,
1259 .futex_waiter = undefined,
12421260 };
12431261 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
12451288 {
12461289 var head = t.worker_threads.load(.monotonic);
12471290 while (true) {
......@@ -2127,26 +2170,34 @@ fn dirCreateDirWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permi
21272170fn dirCreateDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void {
21282171 const t: *Threaded = @ptrCast(@alignCast(userdata));
21292172 _ = t;
2130 try Thread.checkCancel();
21312173
21322174 const sub_path_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
21332175 _ = permissions; // TODO use this value
2134 const sub_dir_handle = windows.OpenFile(sub_path_w.span(), .{
2135 .dir = dir.handle,
2136 .access_mask = .{
2137 .GENERIC = .{ .READ = true },
2138 .STANDARD = .{ .SYNCHRONIZE = true },
2139 },
2140 .creation = .CREATE,
2141 .filter = .dir_only,
2142 }) catch |err| switch (err) {
2143 error.IsDir => return error.Unexpected,
2144 error.PipeBusy => return error.Unexpected,
2145 error.NoDevice => return error.Unexpected,
2146 error.WouldBlock => return error.Unexpected,
2147 error.AntivirusInterference => return error.Unexpected,
2148 else => |e| return e,
2176
2177 const syscall: Syscall = try .start();
2178 const sub_dir_handle = while (true) {
2179 break windows.OpenFile(sub_path_w.span(), .{
2180 .dir = dir.handle,
2181 .access_mask = .{
2182 .GENERIC = .{ .READ = true },
2183 .STANDARD = .{ .SYNCHRONIZE = true },
2184 },
2185 .creation = .CREATE,
2186 .filter = .dir_only,
2187 }) catch |err| switch (err) {
2188 error.IsDir => return syscall.fail(error.Unexpected),
2189 error.PipeBusy => return syscall.fail(error.Unexpected),
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 };
21492199 };
2200 syscall.finish();
21502201 windows.CloseHandle(sub_dir_handle);
21512202}
21522203
......@@ -2225,9 +2276,7 @@ fn dirCreateDirPathOpenWindows(
22252276 .path = sub_path,
22262277 };
22272278
2228 while (true) {
2229 try Thread.checkCancel();
2230
2279 components: while (true) {
22312280 const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, component.path);
22322281 const sub_path_w = sub_path_w_array.span();
22332282 const is_last = it.peekNext() == null;
......@@ -2242,7 +2291,9 @@ fn dirCreateDirPathOpenWindows(
22422291 .Buffer = @constCast(sub_path_w.ptr),
22432292 };
22442293 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(
22462297 &result.handle,
22472298 .{
22482299 .SPECIFIC = .{ .FILE_DIRECTORY = .{
......@@ -2277,16 +2328,20 @@ fn dirCreateDirPathOpenWindows(
22772328 },
22782329 null,
22792330 0,
2280 );
2281
2282 switch (rc) {
2331 )) {
22832332 .SUCCESS => {
2333 syscall.finish();
22842334 component = it.next() orelse return result;
22852335 w.CloseHandle(result.handle);
2336 continue :components;
2337 },
2338 .CANCELLED => {
2339 try syscall.checkCancel();
22862340 continue;
22872341 },
2288 .OBJECT_NAME_INVALID => return error.BadPathName,
2342 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
22892343 .OBJECT_NAME_COLLISION => {
2344 syscall.finish();
22902345 assert(!is_last);
22912346 // stat the file and return an error if it's not a directory
22922347 // this is important because otherwise a dangling symlink
......@@ -2297,23 +2352,24 @@ fn dirCreateDirPathOpenWindows(
22972352 if (fstat.kind != .directory) return error.NotDir;
22982353
22992354 component = it.next().?;
2300 continue;
2355 continue :components;
23012356 },
23022357
23032358 .OBJECT_NAME_NOT_FOUND,
23042359 .OBJECT_PATH_NOT_FOUND,
23052360 => {
2361 syscall.finish();
23062362 component = it.previous() orelse return error.FileNotFound;
2307 continue;
2363 continue :components;
23082364 },
23092365
2310 .NOT_A_DIRECTORY => return error.NotDir,
2366 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
23112367 // This can happen if the directory has 'List folder contents' permission set to 'Deny'
23122368 // and the directory is trying to be opened for iteration.
2313 .ACCESS_DENIED => return error.AccessDenied,
2314 .INVALID_PARAMETER => |err| return w.statusBug(err),
2315 else => return w.unexpectedStatus(rc),
2316 }
2369 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
2370 .INVALID_PARAMETER => |s| return syscall.ntstatusBug(s),
2371 else => |s| return syscall.unexpectedNtstatus(s),
2372 };
23172373 }
23182374}
23192375
......@@ -2637,20 +2693,31 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
26372693fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
26382694 const t: *Threaded = @ptrCast(@alignCast(userdata));
26392695 _ = t;
2640 try Thread.checkCancel();
26412696
26422697 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
26432698 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);
2645 switch (rc) {
2646 .SUCCESS => {},
2647 // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer
2648 // size provided. This is treated as success because the type of variable-length information that this would be relevant for
2649 // (name, volume name, etc) we don't care about.
2650 .BUFFER_OVERFLOW => {},
2651 .INVALID_PARAMETER => |err| return windows.statusBug(err),
2652 .ACCESS_DENIED => return error.AccessDenied,
2653 else => return windows.unexpectedStatus(rc),
2699 {
2700 const syscall: Syscall = try .start();
2701 while (true) switch (windows.ntdll.NtQueryInformationFile(
2702 file.handle,
2703 &io_status_block,
2704 &info,
2705 @sizeOf(windows.FILE.ALL_INFORMATION),
2706 .All,
2707 )) {
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 };
26542721 }
26552722 return .{
26562723 .inode = info.InternalInformation.IndexNumber,
......@@ -2658,15 +2725,25 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
26582725 .permissions = .default_file,
26592726 .kind = if (info.BasicInformation.FileAttributes.REPARSE_POINT) reparse_point: {
26602727 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);
2662 switch (tag_rc) {
2663 .SUCCESS => {},
2728 const syscall: Syscall = try .start();
2729 while (true) switch (windows.ntdll.NtQueryInformationFile(
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(),
26642737 // INFO_LENGTH_MISMATCH and ACCESS_DENIED are the only documented possible errors
26652738 // 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),
2667 .ACCESS_DENIED => return error.AccessDenied,
2668 else => return windows.unexpectedStatus(rc),
2669 }
2739 .INFO_LENGTH_MISMATCH => |err| return syscall.ntstatusBug(err),
2740 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
2741 .CANCELLED => {
2742 try syscall.checkCancel();
2743 continue;
2744 },
2745 else => |s| return syscall.unexpectedNtstatus(s),
2746 };
26702747 if (tag_info.ReparseTag.IsSurrogate) break :reparse_point .sym_link;
26712748 // Unknown reparse point
26722749 break :reparse_point .unknown;
......@@ -2853,7 +2930,6 @@ fn dirAccessWindows(
28532930) Dir.AccessError!void {
28542931 const t: *Threaded = @ptrCast(@alignCast(userdata));
28552932 _ = t;
2856 try Thread.checkCancel();
28572933
28582934 _ = options; // TODO
28592935
......@@ -2879,16 +2955,21 @@ fn dirAccessWindows(
28792955 .SecurityQualityOfService = null,
28802956 };
28812957 var basic_info: windows.FILE.BASIC_INFORMATION = undefined;
2882 switch (windows.ntdll.NtQueryAttributesFile(&attr, &basic_info)) {
2883 .SUCCESS => return,
2884 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
2885 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
2886 .OBJECT_NAME_INVALID => |err| return windows.statusBug(err),
2887 .INVALID_PARAMETER => |err| return windows.statusBug(err),
2888 .ACCESS_DENIED => return error.AccessDenied,
2889 .OBJECT_PATH_SYNTAX_BAD => |err| return windows.statusBug(err),
2890 else => |rc| return windows.unexpectedStatus(rc),
2891 }
2958 const syscall: Syscall = try .start();
2959 while (true) switch (windows.ntdll.NtQueryAttributesFile(&attr, &basic_info)) {
2960 .SUCCESS => return syscall.finish(),
2961 .CANCELLED => {
2962 try syscall.checkCancel();
2963 continue;
2964 },
2965 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
2966 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
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 };
28922973}
28932974
28942975const dirCreateFile = switch (native_os) {
......@@ -3071,27 +3152,40 @@ fn dirCreateFileWindows(
30713152 const w = windows;
30723153 const t: *Threaded = @ptrCast(@alignCast(userdata));
30733154 _ = t;
3074 try Thread.checkCancel();
30753155
30763156 const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, sub_path);
30773157 const sub_path_w = sub_path_w_array.span();
30783158
3079 const handle = try w.OpenFile(sub_path_w, .{
3080 .dir = dir.handle,
3081 .access_mask = .{
3082 .STANDARD = .{ .SYNCHRONIZE = true },
3083 .GENERIC = .{
3084 .WRITE = true,
3085 .READ = flags.read,
3086 },
3087 },
3088 .creation = if (flags.exclusive)
3089 .CREATE
3090 else if (flags.truncate)
3091 .OVERWRITE_IF
3092 else
3093 .OPEN_IF,
3094 });
3159 const handle = handle: {
3160 const syscall: Syscall = try .start();
3161 while (true) {
3162 if (w.OpenFile(sub_path_w, .{
3163 .dir = dir.handle,
3164 .access_mask = .{
3165 .STANDARD = .{ .SYNCHRONIZE = true },
3166 .GENERIC = .{
3167 .WRITE = true,
3168 .READ = flags.read,
3169 },
3170 },
3171 .creation = if (flags.exclusive)
3172 .CREATE
3173 else if (flags.truncate)
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 };
30953189 errdefer w.CloseHandle(handle);
30963190
30973191 var io_status_block: w.IO_STATUS_BLOCK = undefined;
......@@ -3100,7 +3194,8 @@ fn dirCreateFileWindows(
31003194 .shared => false,
31013195 .exclusive => true,
31023196 };
3103 const status = w.ntdll.NtLockFile(
3197 const syscall: Syscall = try .start();
3198 while (true) switch (w.ntdll.NtLockFile(
31043199 handle,
31053200 null,
31063201 null,
......@@ -3111,16 +3206,16 @@ fn dirCreateFileWindows(
31113206 null,
31123207 @intFromBool(flags.lock_nonblocking),
31133208 @intFromBool(exclusive),
3114 );
3115 switch (status) {
3116 .SUCCESS => {},
3117 .INSUFFICIENT_RESOURCES => return error.SystemResources,
3118 .LOCK_NOT_GRANTED => return error.WouldBlock,
3119 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
3120 else => return windows.unexpectedStatus(status),
3121 }
3122
3123 return .{ .handle = handle };
3209 )) {
3210 .SUCCESS => {
3211 syscall.finish();
3212 return .{ .handle = handle };
3213 },
3214 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
3215 .LOCK_NOT_GRANTED => return syscall.fail(error.WouldBlock),
3216 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer
3217 else => |status| return syscall.unexpectedNtstatus(status),
3218 };
31243219}
31253220
31263221fn dirCreateFileWasi(
......@@ -3399,14 +3494,14 @@ fn dirOpenFileWindows(
33993494 flags: File.OpenFlags,
34003495) File.OpenError!File {
34013496 const t: *Threaded = @ptrCast(@alignCast(userdata));
3497 _ = t;
34023498 const sub_path_w_array = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
34033499 const sub_path_w = sub_path_w_array.span();
34043500 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);
34063502}
34073503
34083504pub fn dirOpenFileWtf16(
3409 t: *Threaded,
34103505 dir_handle: ?windows.HANDLE,
34113506 sub_path_w: [:0]const u16,
34123507 flags: File.OpenFlags,
......@@ -3415,7 +3510,6 @@ pub fn dirOpenFileWtf16(
34153510 if (!allow_directory and std.mem.eql(u16, sub_path_w, &.{'.'})) return error.IsDir;
34163511 if (!allow_directory and std.mem.eql(u16, sub_path_w, &.{ '.', '.' })) return error.IsDir;
34173512 const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong;
3418 _ = t;
34193513 const w = windows;
34203514
34213515 var nt_name: w.UNICODE_STRING = .{
......@@ -3437,11 +3531,10 @@ pub fn dirOpenFileWtf16(
34373531 const max_attempts = 13;
34383532 var attempt: u5 = 0;
34393533
3534 var syscall: Syscall = try .start();
34403535 const handle = while (true) {
3441 try Thread.checkCancel();
3442
34433536 var result: w.HANDLE = undefined;
3444 const rc = w.ntdll.NtCreateFile(
3537 switch (w.ntdll.NtCreateFile(
34453538 &result,
34463539 .{
34473540 .STANDARD = .{ .SYNCHRONIZE = true },
......@@ -3463,49 +3556,59 @@ pub fn dirOpenFileWtf16(
34633556 },
34643557 null,
34653558 0,
3466 );
3467 switch (rc) {
3468 .SUCCESS => break result,
3469 .OBJECT_NAME_INVALID => return error.BadPathName,
3470 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
3471 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
3472 .BAD_NETWORK_PATH => return error.NetworkNotFound, // \\server was not found
3473 .BAD_NETWORK_NAME => return error.NetworkNotFound, // \\server was found but \\server\share wasn't
3474 .NO_MEDIA_IN_DEVICE => return error.NoDevice,
3475 .INVALID_PARAMETER => |err| return w.statusBug(err),
3559 )) {
3560 .SUCCESS => {
3561 syscall.finish();
3562 break result;
3563 },
3564 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
3565 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
3566 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
3567 .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found
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 },
34763575 .SHARING_VIOLATION => {
34773576 // This occurs if the file attempting to be opened is a running
34783577 // executable. However, there's a kernel bug: the error may be
34793578 // incorrectly returned for an indeterminate amount of time
34803579 // after an executable file is closed. Here we work around the
34813580 // kernel bug with retry attempts.
3581 syscall.finish();
34823582 if (max_attempts - attempt == 0) return error.SharingViolation;
34833583 _ = w.kernel32.SleepEx((@as(u32, 1) << attempt) >> 1, w.TRUE);
34843584 attempt += 1;
3585 syscall = try .start();
34853586 continue;
34863587 },
3487 .ACCESS_DENIED => return error.AccessDenied,
3488 .PIPE_BUSY => return error.PipeBusy,
3489 .PIPE_NOT_AVAILABLE => return error.NoDevice,
3490 .OBJECT_PATH_SYNTAX_BAD => |err| return w.statusBug(err),
3491 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
3492 .FILE_IS_A_DIRECTORY => return error.IsDir,
3493 .NOT_A_DIRECTORY => return error.NotDir,
3494 .USER_MAPPED_FILE => return error.AccessDenied,
3495 .INVALID_HANDLE => |err| return w.statusBug(err),
3588 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
3589 .PIPE_BUSY => return syscall.fail(error.PipeBusy),
3590 .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice),
3591 .OBJECT_PATH_SYNTAX_BAD => |err| return syscall.ntstatusBug(err),
3592 .OBJECT_NAME_COLLISION => return syscall.fail(error.PathAlreadyExists),
3593 .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir),
3594 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
3595 .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied),
3596 .INVALID_HANDLE => |err| return syscall.ntstatusBug(err),
34963597 .DELETE_PENDING => {
34973598 // This error means that there *was* a file in this location on
34983599 // the file system, but it was deleted. However, the OS is not
34993600 // finished with the deletion operation, and so this CreateFile
35003601 // call has failed. Here, we simulate the kernel bug being
35013602 // fixed by sleeping and retrying until the error goes away.
3603 syscall.finish();
35023604 if (max_attempts - attempt == 0) return error.SharingViolation;
35033605 _ = w.kernel32.SleepEx((@as(u32, 1) << attempt) >> 1, w.TRUE);
35043606 attempt += 1;
3607 syscall = try .start();
35053608 continue;
35063609 },
3507 .VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference,
3508 else => return w.unexpectedStatus(rc),
3610 .VIRUS_INFECTED, .VIRUS_DELETED => return syscall.fail(error.AntivirusInterference),
3611 else => |rc| return syscall.unexpectedNtstatus(rc),
35093612 }
35103613 };
35113614 errdefer w.CloseHandle(handle);
......@@ -3515,7 +3618,8 @@ pub fn dirOpenFileWtf16(
35153618 .shared => false,
35163619 .exclusive => true,
35173620 };
3518 const status = w.ntdll.NtLockFile(
3621 syscall = try .start();
3622 while (true) switch (w.ntdll.NtLockFile(
35193623 handle,
35203624 null,
35213625 null,
......@@ -3526,14 +3630,13 @@ pub fn dirOpenFileWtf16(
35263630 null,
35273631 @intFromBool(flags.lock_nonblocking),
35283632 @intFromBool(exclusive),
3529 );
3530 switch (status) {
3531 .SUCCESS => {},
3532 .INSUFFICIENT_RESOURCES => return error.SystemResources,
3533 .LOCK_NOT_GRANTED => return error.WouldBlock,
3534 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
3535 else => return windows.unexpectedStatus(status),
3536 }
3633 )) {
3634 .SUCCESS => break syscall.finish(),
3635 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
3636 .LOCK_NOT_GRANTED => return syscall.fail(error.WouldBlock),
3637 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer
3638 else => |status| return syscall.unexpectedNtstatus(status),
3639 };
35373640 return .{ .handle = handle };
35383641}
35393642
......@@ -3773,8 +3876,9 @@ pub fn dirOpenDirWindows(
37733876 };
37743877 var io_status_block: w.IO_STATUS_BLOCK = undefined;
37753878 var result: Dir = .{ .handle = undefined };
3776 try Thread.checkCancel();
3777 const rc = w.ntdll.NtCreateFile(
3879
3880 const syscall: Syscall = try .start();
3881 while (true) switch (w.ntdll.NtCreateFile(
37783882 &result.handle,
37793883 // TODO remove some of these flags if options.access_sub_paths is false
37803884 .{
......@@ -3810,21 +3914,26 @@ pub fn dirOpenDirWindows(
38103914 },
38113915 null,
38123916 0,
3813 );
3814
3815 switch (rc) {
3816 .SUCCESS => return result,
3817 .OBJECT_NAME_INVALID => return error.BadPathName,
3818 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
3917 )) {
3918 .SUCCESS => {
3919 syscall.finish();
3920 return result;
3921 },
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),
38193928 .OBJECT_NAME_COLLISION => |err| return w.statusBug(err),
3820 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
3821 .NOT_A_DIRECTORY => return error.NotDir,
3929 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
3930 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
38223931 // This can happen if the directory has 'List folder contents' permission set to 'Deny'
38233932 // and the directory is trying to be opened for iteration.
3824 .ACCESS_DENIED => return error.AccessDenied,
3825 .INVALID_PARAMETER => |err| return w.statusBug(err),
3826 else => return w.unexpectedStatus(rc),
3827 }
3933 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
3934 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),
3935 else => |rc| return syscall.unexpectedNtstatus(rc),
3936 };
38283937}
38293938
38303939fn dirClose(userdata: ?*anyopaque, dirs: []const Dir) void {
......@@ -4264,9 +4373,9 @@ fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) D
42644373 // buffered data.
42654374 if (buffer_index != 0) break;
42664375
4267 try Thread.checkCancel();
42684376 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(
42704379 dr.dir.handle,
42714380 null,
42724381 null,
......@@ -4278,7 +4387,16 @@ fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) D
42784387 w.FALSE,
42794388 null,
42804389 @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 };
42824400 dr.state = .reading;
42834401 if (io_status_block.Information == 0) {
42844402 dr.state = .finished;
......@@ -4466,32 +4584,40 @@ fn dirRealPathFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8,
44664584 const t: *Threaded = @ptrCast(@alignCast(userdata));
44674585 _ = t;
44684586
4469 try Thread.checkCancel();
4470
44714587 var path_name_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
44724588
4473 const h_file = blk: {
4474 const res = windows.OpenFile(path_name_w.span(), .{
4475 .dir = dir.handle,
4476 .access_mask = .{
4477 .GENERIC = .{ .READ = true },
4478 .STANDARD = .{ .SYNCHRONIZE = true },
4479 },
4480 .creation = .OPEN,
4481 .filter = .any,
4482 }) catch |err| switch (err) {
4483 error.WouldBlock => unreachable,
4484 else => |e| return e,
4485 };
4486 break :blk res;
4589 const h_file = handle: {
4590 const syscall: Syscall = try .start();
4591 while (true) {
4592 if (windows.OpenFile(path_name_w.span(), .{
4593 .dir = dir.handle,
4594 .access_mask = .{
4595 .GENERIC = .{ .READ = true },
4596 .STANDARD = .{ .SYNCHRONIZE = true },
4597 },
4598 .creation = .OPEN,
4599 .filter = .any,
4600 })) |handle| {
4601 syscall.finish();
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 }
44874612 };
44884613 defer windows.CloseHandle(h_file);
44894614 return realPathWindows(h_file, out_buffer);
44904615}
44914616
44924617fn realPathWindows(h_file: windows.HANDLE, out_buffer: []u8) File.RealPathError!usize {
4493 // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks
44944618 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();
44954621 const wide_slice = try windows.GetFinalPathNameByHandle(h_file, .{}, &wide_buf);
44964622
44974623 const len = std.unicode.calcWtf8Len(wide_slice);
......@@ -4885,8 +5011,6 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov
48855011 _ = t;
48865012 const w = windows;
48875013
4888 try Thread.checkCancel();
4889
48905014 const sub_path_w_buf = try w.sliceToPrefixedFileW(dir.handle, sub_path);
48915015 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
49095033
49105034 var io_status_block: w.IO_STATUS_BLOCK = undefined;
49115035 var tmp_handle: w.HANDLE = undefined;
4912 var rc = w.ntdll.NtCreateFile(
4913 &tmp_handle,
4914 .{ .STANDARD = .{
4915 .RIGHTS = .{ .DELETE = true },
4916 .SYNCHRONIZE = true,
4917 } },
4918 &.{
4919 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
4920 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
4921 .Attributes = .{},
4922 .ObjectName = &nt_name,
4923 .SecurityDescriptor = null,
4924 .SecurityQualityOfService = null,
4925 },
4926 &io_status_block,
4927 null,
4928 .{},
4929 .VALID_FLAGS,
4930 .OPEN,
4931 .{
4932 .DIRECTORY_FILE = remove_dir,
4933 .NON_DIRECTORY_FILE = !remove_dir,
4934 .OPEN_REPARSE_POINT = true, // would we ever want to delete the target instead?
4935 },
4936 null,
4937 0,
4938 );
4939 switch (rc) {
4940 .SUCCESS => {},
4941 .OBJECT_NAME_INVALID => |err| return w.statusBug(err),
4942 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
4943 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
4944 .BAD_NETWORK_PATH => return error.NetworkNotFound, // \\server was not found
4945 .BAD_NETWORK_NAME => return error.NetworkNotFound, // \\server was found but \\server\share wasn't
4946 .INVALID_PARAMETER => |err| return w.statusBug(err),
4947 .FILE_IS_A_DIRECTORY => return error.IsDir,
4948 .NOT_A_DIRECTORY => return error.NotDir,
4949 .SHARING_VIOLATION => return error.FileBusy,
4950 .ACCESS_DENIED => return error.AccessDenied,
4951 .DELETE_PENDING => return,
4952 else => return w.unexpectedStatus(rc),
5036 {
5037 const syscall: Syscall = try .start();
5038 while (true) switch (w.ntdll.NtCreateFile(
5039 &tmp_handle,
5040 .{ .STANDARD = .{
5041 .RIGHTS = .{ .DELETE = true },
5042 .SYNCHRONIZE = true,
5043 } },
5044 &.{
5045 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
5046 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
5047 .Attributes = .{},
5048 .ObjectName = &nt_name,
5049 .SecurityDescriptor = null,
5050 .SecurityQualityOfService = null,
5051 },
5052 &io_status_block,
5053 null,
5054 .{},
5055 .VALID_FLAGS,
5056 .OPEN,
5057 .{
5058 .DIRECTORY_FILE = remove_dir,
5059 .NON_DIRECTORY_FILE = !remove_dir,
5060 .OPEN_REPARSE_POINT = true, // would we ever want to delete the target instead?
5061 },
5062 null,
5063 0,
5064 )) {
5065 .SUCCESS => break syscall.finish(),
5066 .OBJECT_NAME_INVALID => |err| return syscall.ntstatusBug(err),
5067 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
5068 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
5069 .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found
5070 .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't
5071 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),
5072 .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir),
5073 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
5074 .SHARING_VIOLATION => return syscall.fail(error.FileBusy),
5075 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
5076 .DELETE_PENDING => return syscall.finish(),
5077 else => |rc| return syscall.unexpectedNtstatus(rc),
5078 };
49535079 }
49545080 defer w.CloseHandle(tmp_handle);
49555081
......@@ -4964,9 +5090,7 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov
49645090 //
49655091 // The strategy here is just to try using FileDispositionInformationEx and fall back to
49665092 // FileDispositionInformation if the return value lets us know that some aspect of it is not supported.
4967 const need_fallback = need_fallback: {
4968 try Thread.checkCancel();
4969
5093 const rc = rc: {
49705094 // Deletion with posix semantics if the filesystem supports it.
49715095 const info: w.FILE.DISPOSITION.INFORMATION.EX = .{ .Flags = .{
49725096 .DELETE = true,
......@@ -4974,29 +5098,32 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov
49745098 .IGNORE_READONLY_ATTRIBUTE = true,
49755099 } };
49765100
4977 rc = w.ntdll.NtSetInformationFile(
5101 const syscall: Syscall = try .start();
5102 while (true) switch (w.ntdll.NtSetInformationFile(
49785103 tmp_handle,
49795104 &io_status_block,
49805105 &info,
49815106 @sizeOf(w.FILE.DISPOSITION.INFORMATION.EX),
49825107 .DispositionEx,
4983 );
4984 switch (rc) {
4985 .SUCCESS => return,
5108 )) {
5109 .CANCELLED => {
5110 try syscall.checkCancel();
5111 continue;
5112 },
49865113 // The filesystem does not support FileDispositionInformationEx
49875114 .INVALID_PARAMETER,
49885115 // The operating system does not support FileDispositionInformationEx
49895116 .INVALID_INFO_CLASS,
49905117 // The operating system does not support one of the flags
49915118 .NOT_SUPPORTED,
4992 => break :need_fallback true,
4993 // For all other statuses, fall down to the switch below to handle them.
4994 else => break :need_fallback false,
4995 }
4996 };
5119 => break, // use fallback path below; `syscall` still active
49975120
4998 if (need_fallback) {
4999 try Thread.checkCancel();
5121 // For all other statuses, fall down to the switch below to handle them.
5122 else => |rc| {
5123 syscall.finish();
5124 break :rc rc;
5125 },
5126 };
50005127
50015128 // Deletion with file pending semantics, which requires waiting or moving
50025129 // files to get them removed (from here).
......@@ -5004,14 +5131,23 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov
50045131 .DeleteFile = w.TRUE,
50055132 };
50065133
5007 rc = w.ntdll.NtSetInformationFile(
5134 while (true) switch (w.ntdll.NtSetInformationFile(
50085135 tmp_handle,
50095136 &io_status_block,
50105137 &file_dispo,
50115138 @sizeOf(w.FILE.DISPOSITION.INFORMATION),
50125139 .Disposition,
5013 );
5014 }
5140 )) {
5141 .CANCELLED => {
5142 try syscall.checkCancel();
5143 continue;
5144 },
5145 else => |rc| {
5146 syscall.finish();
5147 break :rc rc;
5148 },
5149 };
5150 };
50155151 switch (rc) {
50165152 .SUCCESS => {},
50175153 .DIRECTORY_NOT_EMPTY => return error.DirNotEmpty,
......@@ -5135,23 +5271,33 @@ fn dirRenameWindows(
51355271 const new_path_w = new_path_w_buf.span();
51365272 const replace_if_exists = true;
51375273
5138 try Thread.checkCancel();
5139
5140 const src_fd = w.OpenFile(old_path_w, .{
5141 .dir = old_dir.handle,
5142 .access_mask = .{
5143 .GENERIC = .{ .WRITE = true },
5144 .STANDARD = .{
5145 .RIGHTS = .{ .DELETE = true },
5146 .SYNCHRONIZE = true,
5147 },
5148 },
5149 .creation = .OPEN,
5150 .filter = .any, // This function is supposed to rename both files and directories.
5151 .follow_symlinks = false,
5152 }) catch |err| switch (err) {
5153 error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`.
5154 else => |e| return e,
5274 const src_fd = src_fd: {
5275 const syscall: Syscall = try .start();
5276 while (true) {
5277 if (w.OpenFile(old_path_w, .{
5278 .dir = old_dir.handle,
5279 .access_mask = .{
5280 .GENERIC = .{ .WRITE = true },
5281 .STANDARD = .{
5282 .RIGHTS = .{ .DELETE = true },
5283 .SYNCHRONIZE = true,
5284 },
5285 },
5286 .creation = .OPEN,
5287 .filter = .any, // This function is supposed to rename both files and directories.
5288 .follow_symlinks = false,
5289 })) |handle| {
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 }
51555301 };
51565302 defer w.CloseHandle(src_fd);
51575303
......@@ -5354,8 +5500,6 @@ fn dirSymLinkWindows(
53545500 _ = t;
53555501 const w = windows;
53565502
5357 try Thread.checkCancel();
5358
53595503 // Target path does not use sliceToPrefixedFileW because certain paths
53605504 // are handled differently when creating a symlink than they would be
53615505 // when converting to an NT namespaced path. CreateSymbolicLink in
......@@ -5385,22 +5529,34 @@ fn dirSymLinkWindows(
53855529 Flags: w.ULONG,
53865530 };
53875531
5388 const symlink_handle = w.OpenFile(sym_link_path_w.span(), .{
5389 .access_mask = .{
5390 .GENERIC = .{ .READ = true, .WRITE = true },
5391 .STANDARD = .{ .SYNCHRONIZE = true },
5392 },
5393 .dir = dir.handle,
5394 .creation = .CREATE,
5395 .filter = if (flags.is_directory) .dir_only else .non_directory_only,
5396 }) catch |err| switch (err) {
5397 error.IsDir => return error.PathAlreadyExists,
5398 error.NotDir => return error.Unexpected,
5399 error.WouldBlock => return error.Unexpected,
5400 error.PipeBusy => return error.Unexpected,
5401 error.NoDevice => return error.Unexpected,
5402 error.AntivirusInterference => return error.Unexpected,
5403 else => |e| return e,
5532 const symlink_handle = handle: {
5533 const syscall: Syscall = try .start();
5534 while (true) {
5535 if (w.OpenFile(sym_link_path_w.span(), .{
5536 .access_mask = .{
5537 .GENERIC = .{ .READ = true, .WRITE = true },
5538 .STANDARD = .{ .SYNCHRONIZE = true },
5539 },
5540 .dir = dir.handle,
5541 .creation = .CREATE,
5542 .filter = if (flags.is_directory) .dir_only else .non_directory_only,
5543 })) |handle| {
5544 syscall.finish();
5545 break :handle handle;
5546 } else |err| switch (err) {
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 }
54045560 };
54055561 defer w.CloseHandle(symlink_handle);
54065562
......@@ -5576,11 +5732,21 @@ fn dirReadLinkWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buf
55765732 _ = t;
55775733 const w = windows;
55785734
5579 try Thread.checkCancel();
5580
55815735 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
55855751 const len = std.unicode.calcWtf8Len(result_w);
55865752 if (len > buffer.len) return error.NameTooLong;
......@@ -5997,17 +6163,25 @@ fn fileSyncWindows(userdata: ?*anyopaque, file: File) File.SyncError!void {
59976163 const t: *Threaded = @ptrCast(@alignCast(userdata));
59986164 _ = t;
59996165
6000 try Thread.checkCancel();
6001
6002 if (windows.kernel32.FlushFileBuffers(file.handle) != 0)
6003 return;
6004
6005 switch (windows.GetLastError()) {
6006 .SUCCESS => return,
6007 .INVALID_HANDLE => unreachable,
6008 .ACCESS_DENIED => return error.AccessDenied, // a sync was performed but the system couldn't update the access time
6009 .UNEXP_NET_ERR => return error.InputOutput,
6010 else => |err| return windows.unexpectedError(err),
6166 const syscall: Syscall = try .start();
6167 while (true) {
6168 if (windows.kernel32.FlushFileBuffers(file.handle) != 0) {
6169 return syscall.finish();
6170 }
6171 switch (windows.GetLastError()) {
6172 .SUCCESS => unreachable, // `FlushFileBuffers` returned nonzero
6173 .INVALID_HANDLE => unreachable,
6174 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), // a sync was performed but the system couldn't update the access time
6175 .UNEXP_NET_ERR => return syscall.fail(error.InputOutput),
6176 .OPERATION_ABORTED => {
6177 try syscall.checkCancel();
6178 continue;
6179 },
6180 else => |err| {
6181 syscall.finish();
6182 return windows.unexpectedError(err);
6183 },
6184 }
60116185 }
60126186}
60136187
......@@ -6074,9 +6248,22 @@ fn fileIsTty(userdata: ?*anyopaque, file: File) Io.Cancelable!bool {
60746248fn isTty(file: File) Io.Cancelable!bool {
60756249 if (is_windows) {
60766250 if (try isCygwinPty(file)) return true;
6077 try Thread.checkCancel();
60786251 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;
60806267 }
60816268
60826269 if (builtin.link_libc) {
......@@ -6146,35 +6333,65 @@ fn fileEnableAnsiEscapeCodes(userdata: ?*anyopaque, file: File) File.EnableAnsiE
61466333 const t: *Threaded = @ptrCast(@alignCast(userdata));
61476334 _ = t;
61486335
6149 if (is_windows) {
6150 try Thread.checkCancel();
6336 if (!is_windows) {
6337 if (try supportsAnsiEscapeCodes(file)) return;
6338 return error.NotTerminalDevice;
6339 }
61516340
6152 // For Windows Terminal, VT Sequences processing is enabled by default.
6153 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;
6341 // For Windows Terminal, VT Sequences processing is enabled by default.
6342 var original_console_mode: windows.DWORD = 0;
61566343
6157 // For Windows Console, VT Sequences processing support was added in Windows 10 build 14361, but disabled by default.
6158 // https://devblogs.microsoft.com/commandline/tmux-support-arrives-for-bash-on-ubuntu-on-windows/
6159 //
6160 // Note: In Microsoft's example for enabling virtual terminal processing, it
6161 // shows attempting to enable `DISABLE_NEWLINE_AUTO_RETURN` as well:
6162 // https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing
6163 // This is avoided because in the old Windows Console, that flag causes \n (as opposed to \r\n)
6164 // to behave unexpectedly (the cursor moves down 1 row but remains on the same column).
6165 // Additionally, the default console mode in Windows Terminal does not have
6166 // `DISABLE_NEWLINE_AUTO_RETURN` set, so by only enabling `ENABLE_VIRTUAL_TERMINAL_PROCESSING`
6167 // we end up matching the mode of Windows Terminal.
6168 const requested_console_modes = windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING;
6169 const console_mode = original_console_mode | requested_console_modes;
6170 try Thread.checkCancel();
6171 if (windows.kernel32.SetConsoleMode(file.handle, console_mode) != 0) return;
6344 {
6345 const syscall: Syscall = try .start();
6346 while (windows.kernel32.GetConsoleMode(file.handle, &original_console_mode) == 0) {
6347 switch (windows.GetLastError()) {
6348 .OPERATION_ABORTED => {
6349 try syscall.checkCancel();
6350 continue;
6351 },
6352 else => {
6353 syscall.finish();
6354 if (try isCygwinPty(file)) return;
6355 return error.NotTerminalDevice;
6356 },
6357 }
61726358 }
6173 if (try isCygwinPty(file)) return;
6174 } else {
6175 if (try supportsAnsiEscapeCodes(file)) return;
6359 syscall.finish();
6360 }
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();
61766394 }
6177 return error.NotTerminalDevice;
61786395}
61796396
61806397fn fileSupportsAnsiEscapeCodes(userdata: ?*anyopaque, file: File) Io.Cancelable!bool {
......@@ -6185,11 +6402,27 @@ fn fileSupportsAnsiEscapeCodes(userdata: ?*anyopaque, file: File) Io.Cancelable!
61856402
61866403fn supportsAnsiEscapeCodes(file: File) Io.Cancelable!bool {
61876404 if (is_windows) {
6188 try Thread.checkCancel();
61896405 var console_mode: windows.DWORD = 0;
6190 if (windows.kernel32.GetConsoleMode(file.handle, &console_mode) != 0) {
6191 if (console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return true;
6406
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 }
61926424 }
6425
61936426 return isCygwinPty(file);
61946427 }
61956428
......@@ -6220,20 +6453,26 @@ fn isCygwinPty(file: File) Io.Cancelable!bool {
62206453 // This allows us to avoid the more costly NtQueryInformationFile call
62216454 // for handles that aren't named pipes.
62226455 {
6223 try Thread.checkCancel();
62246456 var io_status: windows.IO_STATUS_BLOCK = undefined;
62256457 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(
62276460 handle,
62286461 &io_status,
62296462 &device_info,
62306463 @sizeOf(windows.FILE.FS_DEVICE_INFORMATION),
62316464 .Device,
6232 );
6233 switch (rc) {
6234 .SUCCESS => {},
6235 else => return false,
6236 }
6465 )) {
6466 .SUCCESS => break syscall.finish(),
6467 .CANCELLED => {
6468 try syscall.checkCancel();
6469 continue;
6470 },
6471 else => {
6472 syscall.finish();
6473 return false;
6474 },
6475 };
62376476 if (device_info.DeviceType.FileDevice != .NAMED_PIPE) return false;
62386477 }
62396478
......@@ -6248,19 +6487,25 @@ fn isCygwinPty(file: File) Io.Cancelable!bool {
62486487 var name_info_bytes align(@alignOf(windows.FILE.NAME_INFORMATION)) = [_]u8{0} ** (name_bytes_offset + num_name_bytes);
62496488
62506489 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
6251 try Thread.checkCancel();
6252 const rc = windows.ntdll.NtQueryInformationFile(
6490 const syscall: Syscall = try .start();
6491 while (true) switch (windows.ntdll.NtQueryInformationFile(
62536492 handle,
62546493 &io_status_block,
62556494 &name_info_bytes,
62566495 @intCast(name_info_bytes.len),
62576496 .Name,
6258 );
6259 switch (rc) {
6260 .SUCCESS => {},
6497 )) {
6498 .SUCCESS => break syscall.finish(),
6499 .CANCELLED => {
6500 try syscall.checkCancel();
6501 continue;
6502 },
62616503 .INVALID_PARAMETER => unreachable,
6262 else => return false,
6263 }
6504 else => {
6505 syscall.finish();
6506 return false;
6507 },
6508 };
62646509
62656510 const name_info: *const windows.FILE_NAME_INFO = @ptrCast(&name_info_bytes);
62666511 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
62796524 if (signed_len < 0) return error.FileTooBig; // Avoid ambiguous EINVAL errors.
62806525
62816526 if (is_windows) {
6282 try Thread.checkCancel();
6283
62846527 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
62856528 const eof_info: windows.FILE.END_OF_FILE_INFORMATION = .{
62866529 .EndOfFile = signed_len,
62876530 };
62886531
6289 const status = windows.ntdll.NtSetInformationFile(
6532 const syscall: Syscall = try .start();
6533 while (true) switch (windows.ntdll.NtSetInformationFile(
62906534 file.handle,
62916535 &io_status_block,
62926536 &eof_info,
62936537 @sizeOf(windows.FILE.END_OF_FILE_INFORMATION),
62946538 .EndOfFile,
6295 );
6296 switch (status) {
6297 .SUCCESS => return,
6298 .INVALID_HANDLE => |err| return windows.statusBug(err), // Handle not open for writing.
6299 .ACCESS_DENIED => return error.AccessDenied,
6300 .USER_MAPPED_FILE => return error.AccessDenied,
6301 .INVALID_PARAMETER => return error.FileTooBig,
6302 else => return windows.unexpectedStatus(status),
6303 }
6539 )) {
6540 .SUCCESS => return syscall.finish(),
6541 .CANCELLED => {
6542 try syscall.checkCancel();
6543 continue;
6544 },
6545 .INVALID_HANDLE => |err| return syscall.ntstatusBug(err), // Handle not open for writing.
6546 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
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 };
63046551 }
63056552
63066553 if (native_os == .wasi and !builtin.link_libc) {
......@@ -6368,7 +6615,6 @@ fn fileSetPermissions(userdata: ?*anyopaque, file: File, permissions: File.Permi
63686615 _ = t;
63696616 switch (native_os) {
63706617 .windows => {
6371 try Thread.checkCancel();
63726618 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
63736619 const info: windows.FILE.BASIC_INFORMATION = .{
63746620 .CreationTime = 0,
......@@ -6377,19 +6623,23 @@ fn fileSetPermissions(userdata: ?*anyopaque, file: File, permissions: File.Permi
63776623 .ChangeTime = 0,
63786624 .FileAttributes = permissions.toAttributes(),
63796625 };
6380 const status = windows.ntdll.NtSetInformationFile(
6626 const syscall: Syscall = try .start();
6627 while (true) switch (windows.ntdll.NtSetInformationFile(
63816628 file.handle,
63826629 &io_status_block,
63836630 &info,
63846631 @sizeOf(windows.FILE.BASIC_INFORMATION),
63856632 .Basic,
6386 );
6387 switch (status) {
6388 .SUCCESS => return,
6389 .INVALID_HANDLE => |err| return windows.statusBug(err),
6390 .ACCESS_DENIED => return error.AccessDenied,
6391 else => return windows.unexpectedStatus(status),
6392 }
6633 )) {
6634 .SUCCESS => return syscall.finish(),
6635 .CANCELLED => {
6636 try syscall.checkCancel();
6637 continue;
6638 },
6639 .INVALID_HANDLE => |err| return syscall.ntstatusBug(err),
6640 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
6641 else => |status| return syscall.unexpectedNtstatus(status),
6642 };
63936643 },
63946644 .wasi => return error.Unexpected, // Unsupported OS.
63956645 else => return setPermissionsPosix(file.handle, permissions.toMode()),
......@@ -6484,8 +6734,6 @@ fn fileSetTimestamps(
64846734 _ = t;
64856735
64866736 if (is_windows) {
6487 try Thread.checkCancel();
6488
64896737 var access_time_buffer: windows.FILETIME = undefined;
64906738 var modify_time_buffer: windows.FILETIME = undefined;
64916739 var system_time_buffer: windows.LARGE_INTEGER = undefined;
......@@ -6513,13 +6761,22 @@ fn fileSetTimestamps(
65136761 };
65146762
65156763 // https://github.com/ziglang/zig/issues/1840
6516 const rc = windows.kernel32.SetFileTime(file.handle, null, access_ptr, modify_ptr);
6517 if (rc == 0) {
6518 switch (windows.GetLastError()) {
6519 else => |err| return windows.unexpectedError(err),
6764 const syscall: Syscall = try .start();
6765 while (true) {
6766 switch (windows.kernel32.SetFileTime(file.handle, null, access_ptr, modify_ptr)) {
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(),
65206778 }
65216779 }
6522 return;
65236780 }
65246781
65256782 if (native_os == .wasi and !builtin.link_libc) {
......@@ -6601,27 +6858,26 @@ fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!v
66016858 .none => {
66026859 // To match the non-Windows behavior, unlock
66036860 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
6604 const status = windows.ntdll.NtUnlockFile(
6861 while (true) switch (windows.ntdll.NtUnlockFile(
66056862 file.handle,
66066863 &io_status_block,
66076864 &windows_lock_range_off,
66086865 &windows_lock_range_len,
66096866 0,
6610 );
6611 switch (status) {
6612 .SUCCESS => {},
6613 .RANGE_NOT_LOCKED => {},
6867 )) {
6868 .SUCCESS => return,
6869 .CANCELLED => continue,
6870 .RANGE_NOT_LOCKED => return,
66146871 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
6615 else => return windows.unexpectedStatus(status),
6616 }
6617 return;
6872 else => |status| return windows.unexpectedStatus(status),
6873 };
66186874 },
66196875 .shared => false,
66206876 .exclusive => true,
66216877 };
6622 try Thread.checkCancel();
66236878 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(
66256881 file.handle,
66266882 null,
66276883 null,
......@@ -6632,14 +6888,17 @@ fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!v
66326888 null,
66336889 windows.FALSE,
66346890 @intFromBool(exclusive),
6635 );
6636 switch (status) {
6637 .SUCCESS => return,
6638 .INSUFFICIENT_RESOURCES => return error.SystemResources,
6639 .LOCK_NOT_GRANTED => |err| return windows.statusBug(err), // passed FailImmediately=false
6640 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
6641 else => return windows.unexpectedStatus(status),
6642 }
6891 )) {
6892 .SUCCESS => return syscall.finish(),
6893 .CANCELLED => {
6894 try syscall.checkCancel();
6895 continue;
6896 },
6897 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
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 };
66436902 }
66446903
66456904 const operation: i32 = switch (lock) {
......@@ -6680,26 +6939,26 @@ fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockErro
66806939 .none => {
66816940 // To match the non-Windows behavior, unlock
66826941 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
6683 const status = windows.ntdll.NtUnlockFile(
6942 while (true) switch (windows.ntdll.NtUnlockFile(
66846943 file.handle,
66856944 &io_status_block,
66866945 &windows_lock_range_off,
66876946 &windows_lock_range_len,
66886947 0,
6689 );
6690 switch (status) {
6948 )) {
66916949 .SUCCESS => return true,
6950 .CANCELLED => continue,
66926951 .RANGE_NOT_LOCKED => return false,
66936952 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
6694 else => return windows.unexpectedStatus(status),
6695 }
6953 else => |status| return windows.unexpectedStatus(status),
6954 };
66966955 },
66976956 .shared => false,
66986957 .exclusive => true,
66996958 };
6700 try Thread.checkCancel();
67016959 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(
67036962 file.handle,
67046963 null,
67056964 null,
......@@ -6710,14 +6969,23 @@ fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockErro
67106969 null,
67116970 windows.TRUE,
67126971 @intFromBool(exclusive),
6713 );
6714 switch (status) {
6715 .SUCCESS => return true,
6716 .INSUFFICIENT_RESOURCES => return error.SystemResources,
6717 .LOCK_NOT_GRANTED => return false,
6718 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
6719 else => return windows.unexpectedStatus(status),
6720 }
6972 )) {
6973 .SUCCESS => {
6974 syscall.finish();
6975 return true;
6976 },
6977 .LOCK_NOT_GRANTED => {
6978 syscall.finish();
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 };
67216989 }
67226990
67236991 const operation: i32 = switch (lock) {
......@@ -6761,20 +7029,19 @@ fn fileUnlock(userdata: ?*anyopaque, file: File) void {
67617029
67627030 if (is_windows) {
67637031 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
6764 const status = windows.ntdll.NtUnlockFile(
7032 while (true) switch (windows.ntdll.NtUnlockFile(
67657033 file.handle,
67667034 &io_status_block,
67677035 &windows_lock_range_off,
67687036 &windows_lock_range_len,
67697037 0,
6770 );
6771 if (is_debug) switch (status) {
6772 .SUCCESS => {},
6773 .RANGE_NOT_LOCKED => unreachable, // Function asserts unlocked.
6774 .ACCESS_VIOLATION => unreachable, // bad io_status_block pointer
6775 else => unreachable, // Resource deallocation must succeed.
7038 )) {
7039 .SUCCESS => return,
7040 .CANCELLED => continue,
7041 .RANGE_NOT_LOCKED => if (is_debug) unreachable else return, // Function asserts unlocked.
7042 .ACCESS_VIOLATION => if (is_debug) unreachable else return, // bad io_status_block pointer
7043 else => if (is_debug) unreachable else return, // Resource deallocation must succeed.
67767044 };
6777 return;
67787045 }
67797046
67807047 while (true) {
......@@ -6797,14 +7064,14 @@ fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError!
67977064 _ = t;
67987065
67997066 if (is_windows) {
6800 try Thread.checkCancel();
68017067 // On Windows it works like a semaphore + exclusivity flag. To
68027068 // implement this function, we first obtain another lock in shared
68037069 // mode. This changes the exclusivity flag, but increments the
68047070 // semaphore to 2. So we follow up with an NtUnlockFile which
68057071 // decrements the semaphore but does not modify the exclusivity flag.
68067072 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(
68087075 file.handle,
68097076 null,
68107077 null,
......@@ -6816,26 +7083,29 @@ fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError!
68167083 windows.TRUE,
68177084 windows.FALSE,
68187085 )) {
6819 .SUCCESS => {},
6820 .INSUFFICIENT_RESOURCES => |err| return windows.statusBug(err),
6821 .LOCK_NOT_GRANTED => |err| return windows.statusBug(err), // File was not locked in exclusive mode.
6822 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
6823 else => |status| return windows.unexpectedStatus(status),
6824 }
6825 const status = windows.ntdll.NtUnlockFile(
7086 .SUCCESS => break syscall.finish(),
7087 .CANCELLED => {
7088 try syscall.checkCancel();
7089 continue;
7090 },
7091 .INSUFFICIENT_RESOURCES => |err| return syscall.ntstatusBug(err),
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(
68267097 file.handle,
68277098 &io_status_block,
68287099 &windows_lock_range_off,
68297100 &windows_lock_range_len,
68307101 0,
6831 );
6832 if (is_debug) switch (status) {
6833 .SUCCESS => {},
6834 .RANGE_NOT_LOCKED => unreachable, // File was not locked.
6835 .ACCESS_VIOLATION => unreachable, // bad io_status_block pointer
6836 else => unreachable, // Resource deallocation must succeed.
7102 )) {
7103 .SUCCESS => return,
7104 .CANCELLED => continue,
7105 .RANGE_NOT_LOCKED => if (is_debug) unreachable else return, // File was not locked.
7106 .ACCESS_VIOLATION => if (is_debug) unreachable else return, // bad io_status_block pointer
7107 else => if (is_debug) unreachable else return, // Resource deallocation must succeed.
68377108 };
6838 return;
68397109 }
68407110
68417111 const operation = posix.LOCK.SH | posix.LOCK.NB;
......@@ -7158,21 +7428,34 @@ fn fileReadStreamingWindows(userdata: ?*anyopaque, file: File, data: []const []u
71587428 const buffer = data[index];
71597429 const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len);
71607430
7431 const syscall: Syscall = try .start();
71617432 while (true) {
7162 try Thread.checkCancel();
71637433 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();
71657436 return n;
7437 }
71667438 switch (windows.GetLastError()) {
7167 .IO_PENDING => |err| return windows.errorBug(err),
7168 .OPERATION_ABORTED => continue,
7169 .BROKEN_PIPE => return 0,
7170 .HANDLE_EOF => return 0,
7171 .NETNAME_DELETED => return error.ConnectionResetByPeer,
7172 .LOCK_VIOLATION => return error.LockViolation,
7173 .ACCESS_DENIED => return error.AccessDenied,
7174 .INVALID_HANDLE => return error.NotOpenForReading,
7175 else => |err| return windows.unexpectedError(err),
7439 .IO_PENDING => |err| {
7440 syscall.finish();
7441 return windows.errorBug(err);
7442 },
7443 .OPERATION_ABORTED => {
7444 try syscall.checkCancel();
7445 continue;
7446 },
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 },
71767459 }
71777460 }
71787461}
......@@ -7302,21 +7585,34 @@ fn fileReadPositionalWindows(userdata: ?*anyopaque, file: File, data: []const []
73027585 .hEvent = null,
73037586 };
73047587
7588 const syscall: Syscall = try .start();
73057589 while (true) {
7306 try Thread.checkCancel();
73077590 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();
73097593 return n;
7594 }
73107595 switch (windows.GetLastError()) {
7311 .IO_PENDING => |err| return windows.errorBug(err),
7312 .OPERATION_ABORTED => continue,
7313 .BROKEN_PIPE => return 0,
7314 .HANDLE_EOF => return 0,
7315 .NETNAME_DELETED => return error.ConnectionResetByPeer,
7316 .LOCK_VIOLATION => return error.LockViolation,
7317 .ACCESS_DENIED => return error.AccessDenied,
7318 .INVALID_HANDLE => return error.NotOpenForReading,
7319 else => |err| return windows.unexpectedError(err),
7596 .IO_PENDING => |err| {
7597 syscall.finish();
7598 return windows.errorBug(err);
7599 },
7600 .OPERATION_ABORTED => {
7601 try syscall.checkCancel();
7602 continue;
7603 },
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 },
73207616 }
73217617 }
73227618}
......@@ -7355,8 +7651,26 @@ fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!voi
73557651 }
73567652
73577653 if (native_os == .windows) {
7358 try Thread.checkCancel();
7359 return windows.SetFilePointerEx_CURRENT(fd, offset);
7654 const syscall: Syscall = try .start();
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 }
73607674 }
73617675
73627676 if (native_os == .wasi and !builtin.link_libc) {
......@@ -7422,8 +7736,31 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!voi
74227736 const fd = file.handle;
74237737
74247738 if (native_os == .windows) {
7425 try Thread.checkCancel();
7426 return windows.SetFilePointerEx_BEGIN(fd, offset);
7739 // "The starting point is zero or the beginning of the file. If [FILE_BEGIN]
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 }
74277764 }
74287765
74297766 if (native_os == .wasi and !builtin.link_libc) {
......@@ -7527,7 +7864,7 @@ fn processExecutableOpen(userdata: ?*anyopaque, flags: File.OpenFlags) std.proce
75277864 const image_path_unicode_string = &windows.peb().ProcessParameters.ImagePathName;
75287865 const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0];
75297866 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);
75317868 },
75327869 .driverkit,
75337870 .ios,
......@@ -7736,7 +8073,6 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex
77368073 return error.FileNotFound;
77378074 },
77388075 .windows => {
7739 try Thread.checkCancel();
77408076 const w = windows;
77418077 const image_path_unicode_string = &w.peb().ProcessParameters.ImagePathName;
77428078 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
77468082 // that the symlink points to, though, so we need to get the realpath.
77478083 var path_name_w_buf = try w.wToPrefixedFileW(null, image_path_name);
77488084
7749 const h_file = blk: {
7750 const res = w.OpenFile(path_name_w_buf.span(), .{
7751 .dir = null,
7752 .access_mask = .{
7753 .GENERIC = .{ .READ = true },
7754 .STANDARD = .{ .SYNCHRONIZE = true },
7755 },
7756 .creation = .OPEN,
7757 .filter = .any,
7758 }) catch |err| switch (err) {
7759 error.WouldBlock => unreachable,
7760 else => |e| return e,
7761 };
7762 break :blk res;
8085 const h_file = handle: {
8086 const syscall: Syscall = try .start();
8087 while (true) {
8088 if (w.OpenFile(path_name_w_buf.span(), .{
8089 .dir = null,
8090 .access_mask = .{
8091 .GENERIC = .{ .READ = true },
8092 .STANDARD = .{ .SYNCHRONIZE = true },
8093 },
8094 .creation = .OPEN,
8095 .filter = .any,
8096 })) |handle| {
8097 syscall.finish();
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 }
77638108 };
77648109 defer w.CloseHandle(h_file);
77658110
77668111 // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks
8112 try Thread.checkCancel();
77678113 const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, &path_name_w_buf.data);
77688114
77698115 const len = std.unicode.calcWtf8Len(wide_slice);
......@@ -7916,8 +8262,6 @@ fn writeFilePositionalWindows(
79168262 bytes: []const u8,
79178263 offset: u64,
79188264) File.WritePositionalError!usize {
7919 try Thread.checkCancel();
7920
79218265 var bytes_written: windows.DWORD = undefined;
79228266 var overlapped: windows.OVERLAPPED = .{
79238267 .Internal = 0,
......@@ -7931,21 +8275,31 @@ fn writeFilePositionalWindows(
79318275 .hEvent = null,
79328276 };
79338277 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 }
79358284 switch (windows.GetLastError()) {
7936 .INVALID_USER_BUFFER => return error.SystemResources,
7937 .NOT_ENOUGH_MEMORY => return error.SystemResources,
7938 .OPERATION_ABORTED => return error.Canceled,
7939 .NOT_ENOUGH_QUOTA => return error.SystemResources,
7940 .NO_DATA => return error.BrokenPipe,
7941 .INVALID_HANDLE => return error.NotOpenForWriting,
7942 .LOCK_VIOLATION => return error.LockViolation,
7943 .ACCESS_DENIED => return error.AccessDenied,
7944 .WORKING_SET_QUOTA => return error.SystemResources,
7945 else => |err| return windows.unexpectedError(err),
8285 .OPERATION_ABORTED => {
8286 try syscall.checkCancel();
8287 continue;
8288 },
8289 .INVALID_USER_BUFFER => return syscall.fail(error.SystemResources),
8290 .NOT_ENOUGH_MEMORY => return syscall.fail(error.SystemResources),
8291 .NOT_ENOUGH_QUOTA => return syscall.fail(error.SystemResources),
8292 .NO_DATA => return syscall.fail(error.BrokenPipe),
8293 .INVALID_HANDLE => return syscall.fail(error.NotOpenForWriting),
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 },
79468301 }
79478302 }
7948 return bytes_written;
79498303}
79508304
79518305fn fileWriteStreaming(
......@@ -8078,25 +8432,33 @@ fn writeFileStreamingWindows(
80788432 handle: windows.HANDLE,
80798433 bytes: []const u8,
80808434) File.Writer.Error!usize {
8081 try Thread.checkCancel();
8082
80838435 var bytes_written: windows.DWORD = undefined;
80848436 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 }
80868443 switch (windows.GetLastError()) {
8087 .INVALID_USER_BUFFER => return error.SystemResources,
8088 .NOT_ENOUGH_MEMORY => return error.SystemResources,
8089 .OPERATION_ABORTED => return error.Canceled,
8090 .NOT_ENOUGH_QUOTA => return error.SystemResources,
8091 .NO_DATA => return error.BrokenPipe,
8092 .INVALID_HANDLE => return error.NotOpenForWriting,
8093 .LOCK_VIOLATION => return error.LockViolation,
8094 .ACCESS_DENIED => return error.AccessDenied,
8095 .WORKING_SET_QUOTA => return error.SystemResources,
8096 else => |err| return windows.unexpectedError(err),
8444 .OPERATION_ABORTED => {
8445 try syscall.checkCancel();
8446 continue;
8447 },
8448 .INVALID_USER_BUFFER => return syscall.fail(error.SystemResources),
8449 .NOT_ENOUGH_MEMORY => return syscall.fail(error.SystemResources),
8450 .NOT_ENOUGH_QUOTA => return syscall.fail(error.SystemResources),
8451 .NO_DATA => return syscall.fail(error.BrokenPipe),
8452 .INVALID_HANDLE => return syscall.fail(error.NotOpenForWriting),
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 },
80978460 }
80988461 }
8099 return bytes_written;
81008462}
81018463
81028464fn fileWriteFileStreaming(
......@@ -8716,9 +9078,7 @@ fn fileWriteFilePositional(
87169078 return error.Unimplemented;
87179079}
87189080
8719fn nowPosix(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
8720 const t: *Threaded = @ptrCast(@alignCast(userdata));
8721 _ = t;
9081fn nowPosix(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
87229082 const clock_id: posix.clockid_t = clockToPosix(clock);
87239083 var tp: posix.timespec = undefined;
87249084 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
87289088 }
87299089}
87309090
8731const now = switch (native_os) {
8732 .windows => nowWindows,
8733 .wasi => nowWasi,
8734 else => nowPosix,
8735};
8736
8737fn nowWindows(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
9091fn now(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
87389092 const t: *Threaded = @ptrCast(@alignCast(userdata));
87399093 _ = 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 {
87409102 switch (clock) {
87419103 .real => {
87429104 // RtlGetSystemTimePrecise() has a granularity of 100 nanoseconds
......@@ -8769,25 +9131,24 @@ fn nowWindows(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestam
87699131 }
87709132}
87719133
8772fn nowWasi(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
8773 const t: *Threaded = @ptrCast(@alignCast(userdata));
8774 _ = t;
9134fn nowWasi(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
87759135 var ns: std.os.wasi.timestamp_t = undefined;
87769136 const err = std.os.wasi.clock_time_get(clockToWasi(clock), 1, &ns);
87779137 if (err != .SUCCESS) return error.Unexpected;
87789138 return .fromNanoseconds(ns);
87799139}
87809140
8781const sleep = switch (native_os) {
8782 .windows => sleepWindows,
8783 .wasi => sleepWasi,
8784 .linux => sleepLinux,
8785 else => sleepPosix,
8786};
8787
8788fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
9141fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
87899142 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 {
87919152 const clock_id: posix.clockid_t = clockToPosix(switch (timeout) {
87929153 .none => .awake,
87939154 .duration => |d| d.clock,
......@@ -8824,21 +9185,7 @@ fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
88249185 }
88259186}
88269187
8827fn sleepWindows(userdata: ?*anyopaque, 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));
9188fn sleepWasi(t: *Threaded, timeout: Io.Timeout) Io.SleepError!void {
88429189 const t_io = ioBasic(t);
88439190 const w = std.os.wasi;
88449191
......@@ -8867,8 +9214,7 @@ fn sleepWasi(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
88679214 syscall.finish();
88689215}
88699216
8870fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
8871 const t: *Threaded = @ptrCast(@alignCast(userdata));
9217fn sleepPosix(t: *Threaded, timeout: Io.Timeout) Io.SleepError!void {
88729218 const t_io = ioBasic(t);
88739219 const sec_type = @typeInfo(posix.timespec).@"struct".fields[0].type;
88749220 const nsec_type = @typeInfo(posix.timespec).@"struct".fields[1].type;
......@@ -9037,93 +9383,90 @@ fn netListenIpWindows(
90379383 var storage: WsaAddress = undefined;
90389384 var addr_len = addressToWsa(&address, &storage);
90399385
9040 {
9041 const syscall: Syscall = try .start();
9042 while (true) {
9043 const rc = ws2_32.bind(socket_handle, &storage.any, addr_len);
9044 if (rc != ws2_32.SOCKET_ERROR) {
9045 syscall.finish();
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 }
9386 var syscall: Syscall = try .start();
9387 while (true) {
9388 const rc = ws2_32.bind(socket_handle, &storage.any, addr_len);
9389 if (rc != ws2_32.SOCKET_ERROR) {
9390 syscall.finish();
9391 break;
90739392 }
9074 }
9075 {
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) {
9393 switch (ws2_32.WSAGetLastError()) {
9394 .NOTINITIALISED => {
90809395 syscall.finish();
9081 break;
9082 }
9083 switch (ws2_32.WSAGetLastError()) {
9084 .EINTR => {
9085 try syscall.checkCancel();
9086 continue;
9087 },
9088 .NOTINITIALISED => {
9089 try initializeWsa(t);
9090 try syscall.checkCancel();
9091 continue;
9092 },
9093 else => |e| {
9094 syscall.finish();
9095 switch (e) {
9096 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
9097 .ENETDOWN => return error.NetworkDown,
9098 .EADDRINUSE => return error.AddressInUse,
9099 .EISCONN => |err| return wsaErrorBug(err),
9100 .EINVAL => |err| return wsaErrorBug(err),
9101 .EMFILE, .ENOBUFS => return error.SystemResources,
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 }
9396 try initializeWsa(t);
9397 syscall = try .start();
9398 continue;
9399 },
9400 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
9401 try syscall.checkCancel();
9402 continue;
9403 },
9404 else => |e| {
9405 syscall.finish();
9406 switch (e) {
9407 .EADDRINUSE => return error.AddressInUse,
9408 .EADDRNOTAVAIL => return error.AddressUnavailable,
9409 .ENOTSOCK => |err| return wsaErrorBug(err),
9410 .EFAULT => |err| return wsaErrorBug(err),
9411 .EINVAL => |err| return wsaErrorBug(err),
9412 .ENOBUFS => return error.SystemResources,
9413 .ENETDOWN => return error.NetworkDown,
9414 else => |err| return windows.unexpectedWSAError(err),
9415 }
9416 },
91099417 }
91109418 }
91119419
9112 try wsaGetSockName(t, socket_handle, &storage.any, &addr_len);
9113
9114 return .{
9115 .socket = .{
9116 .handle = socket_handle,
9117 .address = addressFromWsa(&storage),
9118 },
9119 };
9120}
9121
9122fn netListenIpUnavailable(
9123 userdata: ?*anyopaque,
9124 address: IpAddress,
9125 options: IpAddress.ListenOptions,
9126) IpAddress.ListenError!net.Server {
9420 syscall = try .start();
9421 while (true) {
9422 const rc = ws2_32.listen(socket_handle, options.kernel_backlog);
9423 if (rc != ws2_32.SOCKET_ERROR) {
9424 syscall.finish();
9425 break;
9426 }
9427 switch (ws2_32.WSAGetLastError()) {
9428 .NOTINITIALISED => {
9429 syscall.finish();
9430 try initializeWsa(t);
9431 syscall = try .start();
9432 continue;
9433 },
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 {
91279470 _ = userdata;
91289471 _ = address;
91299472 _ = options;
......@@ -9193,24 +9536,24 @@ fn netListenUnixWindows(
91939536 var storage: WsaAddress = undefined;
91949537 const addr_len = addressUnixToWsa(address, &storage);
91959538
9196 const syscall: Syscall = try .start();
9539 var syscall: Syscall = try .start();
91979540 while (true) {
91989541 const rc = ws2_32.bind(socket_handle, &storage.any, addr_len);
91999542 if (rc != ws2_32.SOCKET_ERROR) break;
92009543 switch (ws2_32.WSAGetLastError()) {
9201 .EINTR => {
9202 try syscall.checkCancel();
9203 continue;
9204 },
92059544 .NOTINITIALISED => {
9545 syscall.finish();
92069546 try initializeWsa(t);
9547 syscall = try .start();
9548 continue;
9549 },
9550 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
92079551 try syscall.checkCancel();
92089552 continue;
92099553 },
92109554 else => |e| {
92119555 syscall.finish();
92129556 switch (e) {
9213 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
92149557 .EADDRINUSE => return error.AddressInUse,
92159558 .EADDRNOTAVAIL => return error.AddressUnavailable,
92169559 .ENOTSOCK => |err| return wsaErrorBug(err),
......@@ -9232,15 +9575,16 @@ fn netListenUnixWindows(
92329575 return socket_handle;
92339576 }
92349577 switch (ws2_32.WSAGetLastError()) {
9235 .EINTR => continue,
9578 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => continue,
92369579 .NOTINITIALISED => {
9580 syscall.finish();
92379581 try initializeWsa(t);
9582 syscall = try .start();
92389583 continue;
92399584 },
92409585 else => |e| {
92419586 syscall.finish();
92429587 switch (e) {
9243 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
92449588 .ENETDOWN => return error.NetworkDown,
92459589 .EADDRINUSE => return error.AddressInUse,
92469590 .EISCONN => |err| return wsaErrorBug(err),
......@@ -9469,7 +9813,7 @@ fn wsaGetSockName(
94699813 addr: *ws2_32.sockaddr,
94709814 addr_len: *i32,
94719815) !void {
9472 const syscall: Syscall = try .start();
9816 var syscall: Syscall = try .start();
94739817 while (true) {
94749818 const rc = ws2_32.getsockname(handle, addr, addr_len);
94759819 if (rc != ws2_32.SOCKET_ERROR) {
......@@ -9477,19 +9821,19 @@ fn wsaGetSockName(
94779821 return;
94789822 }
94799823 switch (ws2_32.WSAGetLastError()) {
9480 .EINTR => {
9824 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
94819825 try syscall.checkCancel();
94829826 continue;
94839827 },
94849828 .NOTINITIALISED => {
9829 syscall.finish();
94859830 try initializeWsa(t);
9486 try syscall.checkCancel();
9831 syscall = try .start();
94879832 continue;
94889833 },
94899834 else => |e| {
94909835 syscall.finish();
94919836 switch (e) {
9492 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
94939837 .ENETDOWN => return error.NetworkDown,
94949838 .EFAULT => |err| return wsaErrorBug(err),
94959839 .ENOTSOCK => |err| return wsaErrorBug(err),
......@@ -9530,21 +9874,30 @@ fn setSocketOption(fd: posix.fd_t, level: i32, opt_name: u32, option: u32) !void
95309874
95319875fn setSocketOptionWsa(t: *Threaded, socket: Io.net.Socket.Handle, level: i32, opt_name: u32, option: u32) !void {
95329876 const o: []const u8 = @ptrCast(&option);
9877 var syscall: Syscall = try .start();
95339878 const rc = ws2_32.setsockopt(socket, level, @bitCast(opt_name), o.ptr, @intCast(o.len));
95349879 while (true) {
9535 if (rc != ws2_32.SOCKET_ERROR) return;
9880 if (rc != ws2_32.SOCKET_ERROR) return syscall.finish();
95369881 switch (ws2_32.WSAGetLastError()) {
9537 .EINTR => continue,
9538 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
9882 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
9883 try syscall.checkCancel();
9884 continue;
9885 },
95399886 .NOTINITIALISED => {
9887 syscall.finish();
95409888 try initializeWsa(t);
9889 syscall = try .start();
95419890 continue;
95429891 },
9543 .ENETDOWN => return error.NetworkDown,
9544 .EFAULT => |err| return wsaErrorBug(err),
9545 .ENOTSOCK => |err| return wsaErrorBug(err),
9546 .EINVAL => |err| return wsaErrorBug(err),
9547 else => |err| return windows.unexpectedWSAError(err),
9892 .ENETDOWN => return syscall.fail(error.NetworkDown),
9893 .EFAULT, .ENOTSOCK, .EINVAL => |err| {
9894 syscall.finish();
9895 return wsaErrorBug(err);
9896 },
9897 else => |err| {
9898 syscall.finish();
9899 return windows.unexpectedWSAError(err);
9900 },
95489901 }
95499902 }
95509903}
......@@ -9592,7 +9945,7 @@ fn netConnectIpWindows(
95929945 var storage: WsaAddress = undefined;
95939946 var addr_len = addressToWsa(address, &storage);
95949947
9595 const syscall: Syscall = try .start();
9948 var syscall: Syscall = try .start();
95969949 while (true) {
95979950 const rc = ws2_32.connect(socket_handle, &storage.any, addr_len);
95989951 if (rc != ws2_32.SOCKET_ERROR) {
......@@ -9600,19 +9953,19 @@ fn netConnectIpWindows(
96009953 break;
96019954 }
96029955 switch (ws2_32.WSAGetLastError()) {
9603 .EINTR => {
9956 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
96049957 try syscall.checkCancel();
96059958 continue;
96069959 },
96079960 .NOTINITIALISED => {
9961 syscall.finish();
96089962 try initializeWsa(t);
9609 try syscall.checkCancel();
9963 syscall = try .start();
96109964 continue;
96119965 },
96129966 else => |e| {
96139967 syscall.finish();
96149968 switch (e) {
9615 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
96169969 .EADDRNOTAVAIL => return error.AddressUnavailable,
96179970 .ECONNREFUSED => return error.ConnectionRefused,
96189971 .ECONNRESET => return error.ConnectionResetByPeer,
......@@ -9682,27 +10035,36 @@ fn netConnectUnixWindows(
968210035 var storage: WsaAddress = undefined;
968310036 const addr_len = addressUnixToWsa(address, &storage);
968410037
10038 var syscall: Syscall = try .start();
968510039 while (true) {
968610040 const rc = ws2_32.connect(socket_handle, &storage.any, addr_len);
968710041 if (rc != ws2_32.SOCKET_ERROR) break;
968810042 switch (ws2_32.WSAGetLastError()) {
9689 .EINTR => continue,
9690 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
10043 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
10044 try syscall.checkCancel();
10045 continue;
10046 },
969110047 .NOTINITIALISED => {
10048 syscall.finish();
969210049 try initializeWsa(t);
10050 syscall = try .start();
969310051 continue;
969410052 },
9695
9696 .ECONNREFUSED => return error.FileNotFound,
9697 .EFAULT => |err| return wsaErrorBug(err),
9698 .EINVAL => |err| return wsaErrorBug(err),
9699 .EISCONN => |err| return wsaErrorBug(err),
9700 .ENOTSOCK => |err| return wsaErrorBug(err),
9701 .EWOULDBLOCK => return error.WouldBlock,
9702 .EACCES => return error.AccessDenied,
9703 .ENOBUFS => return error.SystemResources,
9704 .EAFNOSUPPORT => return error.AddressFamilyUnsupported,
9705 else => |err| return windows.unexpectedWSAError(err),
10053 else => |e| {
10054 syscall.finish();
10055 switch (e) {
10056 .ECONNREFUSED => return error.FileNotFound,
10057 .EFAULT => |err| return wsaErrorBug(err),
10058 .EINVAL => |err| return wsaErrorBug(err),
10059 .EISCONN => |err| return wsaErrorBug(err),
10060 .ENOTSOCK => |err| return wsaErrorBug(err),
10061 .EWOULDBLOCK => return error.WouldBlock,
10062 .EACCES => return error.AccessDenied,
10063 .ENOBUFS => return error.SystemResources,
10064 .EAFNOSUPPORT => return error.AddressFamilyUnsupported,
10065 else => |err| return windows.unexpectedWSAError(err),
10066 }
10067 },
970610068 }
970710069 }
970810070
......@@ -9756,7 +10118,7 @@ fn netBindIpWindows(
975610118 var storage: WsaAddress = undefined;
975710119 var addr_len = addressToWsa(address, &storage);
975810120
9759 const syscall: Syscall = try .start();
10121 var syscall: Syscall = try .start();
976010122 while (true) {
976110123 const rc = ws2_32.bind(socket_handle, &storage.any, addr_len);
976210124 if (rc != ws2_32.SOCKET_ERROR) {
......@@ -9764,19 +10126,19 @@ fn netBindIpWindows(
976410126 break;
976510127 }
976610128 switch (ws2_32.WSAGetLastError()) {
9767 .EINTR => {
10129 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
976810130 try syscall.checkCancel();
976910131 continue;
977010132 },
977110133 .NOTINITIALISED => {
10134 syscall.finish();
977210135 try initializeWsa(t);
9773 try syscall.checkCancel();
10136 syscall = try .start();
977410137 continue;
977510138 },
977610139 else => |e| {
977710140 syscall.finish();
977810141 switch (e) {
9779 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
978010142 .EADDRINUSE => return error.AddressInUse,
978110143 .EADDRNOTAVAIL => return error.AddressUnavailable,
978210144 .ENOTSOCK => |err| return wsaErrorBug(err),
......@@ -9886,7 +10248,7 @@ fn openSocketWsa(
988610248 const mode = posixSocketMode(options.mode);
988710249 const protocol = posixProtocol(options.protocol);
988810250 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();
989010252 while (true) {
989110253 const rc = ws2_32.WSASocketW(family, @bitCast(mode), @bitCast(protocol), null, 0, flags);
989210254 if (rc != ws2_32.INVALID_SOCKET) {
......@@ -9894,19 +10256,19 @@ fn openSocketWsa(
989410256 return rc;
989510257 }
989610258 switch (ws2_32.WSAGetLastError()) {
9897 .EINTR => {
10259 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
989810260 try syscall.checkCancel();
989910261 continue;
990010262 },
990110263 .NOTINITIALISED => {
10264 syscall.finish();
990210265 try initializeWsa(t);
9903 try syscall.checkCancel();
10266 syscall = try .start();
990410267 continue;
990510268 },
990610269 else => |e| {
990710270 syscall.finish();
990810271 switch (e) {
9909 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
991010272 .EAFNOSUPPORT => return error.AddressFamilyUnsupported,
991110273 .EMFILE => return error.ProcessFdQuotaExceeded,
991210274 .ENOBUFS => return error.SystemResources,
......@@ -9984,7 +10346,7 @@ fn netAcceptWindows(userdata: ?*anyopaque, listen_handle: net.Socket.Handle) net
998410346 const t: *Threaded = @ptrCast(@alignCast(userdata));
998510347 var storage: WsaAddress = undefined;
998610348 var addr_len: i32 = @sizeOf(WsaAddress);
9987 const syscall: Syscall = try .start();
10349 var syscall: Syscall = try .start();
998810350 while (true) {
998910351 const rc = ws2_32.accept(listen_handle, &storage.any, &addr_len);
999010352 if (rc != ws2_32.INVALID_SOCKET) {
......@@ -9995,19 +10357,19 @@ fn netAcceptWindows(userdata: ?*anyopaque, listen_handle: net.Socket.Handle) net
999510357 } };
999610358 }
999710359 switch (ws2_32.WSAGetLastError()) {
9998 .EINTR => {
10360 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
999910361 try syscall.checkCancel();
1000010362 continue;
1000110363 },
1000210364 .NOTINITIALISED => {
10365 syscall.finish();
1000310366 try initializeWsa(t);
10004 try syscall.checkCancel();
10367 syscall = try .start();
1000510368 continue;
1000610369 },
1000710370 else => |e| {
1000810371 syscall.finish();
1000910372 switch (e) {
10010 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
1001110373 .ECONNRESET => return error.ConnectionAborted,
1001210374 .EFAULT => |err| return wsaErrorBug(err),
1001310375 .ENOTSOCK => |err| return wsaErrorBug(err),
......@@ -10141,48 +10503,41 @@ fn netReadWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, data: [][]u8
1014110503 break :b bufs;
1014210504 };
1014310505
10506 var syscall: Syscall = try .start();
1014410507 while (true) {
10145 try Thread.checkCancel();
10146
1014710508 var flags: u32 = 0;
10148 var overlapped: windows.OVERLAPPED = std.mem.zeroes(windows.OVERLAPPED);
1014910509 var n: u32 = undefined;
10150 const rc = ws2_32.WSARecv(handle, bufs.ptr, @intCast(bufs.len), &n, &flags, &overlapped, null);
10151 if (rc != ws2_32.SOCKET_ERROR) return n;
10152 const wsa_error: ws2_32.WinsockError = switch (ws2_32.WSAGetLastError()) {
10153 .IO_PENDING => e: {
10154 var result_flags: u32 = undefined;
10155 const overlapped_rc = ws2_32.WSAGetOverlappedResult(
10156 handle,
10157 &overlapped,
10158 &n,
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 }
10510 const rc = ws2_32.WSARecv(handle, bufs.ptr, @intCast(bufs.len), &n, &flags, null, null);
10511 if (rc != ws2_32.SOCKET_ERROR) {
10512 syscall.finish();
10513 return n;
10514 }
10515 switch (ws2_32.WSAGetLastError()) {
10516 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
10517 try syscall.checkCancel();
10518 continue;
1016710519 },
10168 else => |err| err,
10169 };
10170 switch (wsa_error) {
10171 .EINTR => continue,
10172 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
1017310520 .NOTINITIALISED => {
10521 syscall.finish();
1017410522 try initializeWsa(t);
10523 syscall = try .start();
1017510524 continue;
1017610525 },
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),
1017910531 .EFAULT => unreachable, // a pointer is not completely contained in user address space.
10180 .EINVAL => |err| return wsaErrorBug(err),
10181 .EMSGSIZE => |err| return wsaErrorBug(err),
10182 .ENETDOWN => return error.NetworkDown,
10183 .ENETRESET => return error.ConnectionResetByPeer,
10184 .ENOTCONN => return error.SocketUnconnected,
10185 else => |err| return windows.unexpectedWSAError(err),
10532
10533 else => |err| {
10534 syscall.finish();
10535 switch (err) {
10536 .EINVAL => return wsaErrorBug(err),
10537 .EMSGSIZE => return wsaErrorBug(err),
10538 else => return windows.unexpectedWSAError(err),
10539 }
10540 },
1018610541 }
1018710542 }
1018810543}
......@@ -10269,7 +10624,7 @@ fn netSendOne(
1026910624 .controllen = @intCast(message.control.len),
1027010625 .flags = 0,
1027110626 };
10272 const syscall: Syscall = try .start();
10627 var syscall: Syscall = try .start();
1027310628 while (true) {
1027410629 const rc = posix.system.sendmsg(handle, &msg, flags);
1027510630 if (is_windows) {
......@@ -10279,19 +10634,19 @@ fn netSendOne(
1027910634 return;
1028010635 }
1028110636 switch (ws2_32.WSAGetLastError()) {
10282 .EINTR => {
10637 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
1028310638 try syscall.checkCancel();
1028410639 continue;
1028510640 },
1028610641 .NOTINITIALISED => {
10642 syscall.finish();
1028710643 try initializeWsa(t);
10288 try syscall.checkCancel();
10644 syscall = try .start();
1028910645 continue;
1029010646 },
1029110647 else => |e| {
1029210648 syscall.finish();
1029310649 switch (e) {
10294 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
1029510650 .EACCES => return error.AccessDenied,
1029610651 .EADDRNOTAVAIL => return error.AddressUnavailable,
1029710652 .ECONNRESET => return error.ConnectionResetByPeer,
......@@ -10729,49 +11084,44 @@ fn netWriteWindows(
1072911084 },
1073011085 };
1073111086
11087 var syscall: Syscall = try .start();
1073211088 while (true) {
10733 try Thread.checkCancel();
10734
1073511089 var n: u32 = undefined;
10736 var overlapped: windows.OVERLAPPED = std.mem.zeroes(windows.OVERLAPPED);
10737 const rc = ws2_32.WSASend(handle, &iovecs, len, &n, 0, &overlapped, null);
10738 if (rc != ws2_32.SOCKET_ERROR) return n;
10739 const wsa_error: ws2_32.WinsockError = switch (ws2_32.WSAGetLastError()) {
10740 .IO_PENDING => e: {
10741 var result_flags: u32 = undefined;
10742 const overlapped_rc = ws2_32.WSAGetOverlappedResult(
10743 handle,
10744 &overlapped,
10745 &n,
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 }
11090 const rc = ws2_32.WSASend(handle, &iovecs, len, &n, 0, null, null);
11091 if (rc != ws2_32.SOCKET_ERROR) {
11092 syscall.finish();
11093 return n;
11094 }
11095 switch (ws2_32.WSAGetLastError()) {
11096 .IO_PENDING => unreachable, // not overlapped
11097 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
11098 try syscall.checkCancel();
11099 continue;
1075411100 },
10755 else => |err| err,
10756 };
10757 switch (wsa_error) {
10758 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => continue,
1075911101 .NOTINITIALISED => {
11102 syscall.finish();
1076011103 try initializeWsa(t);
11104 syscall = try .start();
1076111105 continue;
1076211106 },
1076311107
10764 .ECONNABORTED => return error.ConnectionResetByPeer,
10765 .ECONNRESET => return error.ConnectionResetByPeer,
10766 .EINVAL => return error.SocketUnconnected,
10767 .ENETDOWN => return error.NetworkDown,
10768 .ENETRESET => return error.ConnectionResetByPeer,
10769 .ENOBUFS => return error.SystemResources,
10770 .ENOTCONN => return error.SocketUnconnected,
10771 .ENOTSOCK => |err| return wsaErrorBug(err),
10772 .EOPNOTSUPP => |err| return wsaErrorBug(err),
10773 .ESHUTDOWN => |err| return wsaErrorBug(err),
10774 else => |err| return windows.unexpectedWSAError(err),
11108 .ECONNABORTED => return syscall.fail(error.ConnectionResetByPeer),
11109 .ECONNRESET => return syscall.fail(error.ConnectionResetByPeer),
11110 .EINVAL => return syscall.fail(error.SocketUnconnected),
11111 .ENETDOWN => return syscall.fail(error.NetworkDown),
11112 .ENETRESET => return syscall.fail(error.ConnectionResetByPeer),
11113 .ENOBUFS => return syscall.fail(error.SystemResources),
11114 .ENOTCONN => return syscall.fail(error.SocketUnconnected),
11115
11116 else => |err| {
11117 syscall.finish();
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 },
1077511125 }
1077611126 }
1077711127}
......@@ -10872,7 +11222,6 @@ fn netShutdownPosix(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.S
1087211222fn netShutdownWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.ShutdownHow) net.ShutdownError!void {
1087311223 if (!have_networking) return error.NetworkDown;
1087411224 const t: *Threaded = @ptrCast(@alignCast(userdata));
10875 const current_thread = Thread.getCurrent(t);
1087611225
1087711226 const wsa_how: i32 = switch (how) {
1087811227 .recv => ws2_32.SD_RECEIVE,
......@@ -10880,27 +11229,27 @@ fn netShutdownWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net
1088011229 .both => ws2_32.SD_BOTH,
1088111230 };
1088211231
10883 try current_thread.beginSyscall();
11232 var syscall: Syscall = try .start();
1088411233 while (true) {
1088511234 const rc = ws2_32.shutdown(handle, wsa_how);
1088611235 if (rc != ws2_32.SOCKET_ERROR) {
10887 current_thread.endSyscall();
11236 syscall.finish();
1088811237 return;
1088911238 }
1089011239 switch (ws2_32.WSAGetLastError()) {
10891 .EINTR => {
10892 try current_thread.checkCancel();
11240 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
11241 try syscall.checkCancel();
1089311242 continue;
1089411243 },
1089511244 .NOTINITIALISED => {
11245 syscall.finish();
1089611246 try initializeWsa(t);
10897 try current_thread.checkCancel();
11247 syscall = try .start();
1089811248 continue;
1089911249 },
1090011250 else => |e| {
10901 current_thread.endSyscall();
11251 syscall.finish();
1090211252 switch (e) {
10903 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
1090411253 .ECONNABORTED => return error.ConnectionAborted,
1090511254 .ECONNRESET => return error.ConnectionResetByPeer,
1090611255 .ENETDOWN => return error.NetworkDown,
......@@ -11093,18 +11442,17 @@ fn netLookupFallible(
1109311442 .provider = null,
1109411443 .next = null,
1109511444 };
11096 const cancel_handle: ?*windows.HANDLE = null;
1109711445 var res: *ws2_32.ADDRINFOEXW = undefined;
1109811446 const timeout: ?*ws2_32.timeval = null;
1109911447 while (true) {
11448 // TODO: hook this up to cancelation with `Thread.Status.cancelation.blocked_windows_dns`.
11449 // See matching TODO in `Thread.cancelAwaitable`.
1110011450 try Thread.checkCancel();
11101 // TODO make this append to the queue eagerly rather than blocking until
11102 // the whole thing finishes
11103 const rc: ws2_32.WinsockError = @enumFromInt(ws2_32.GetAddrInfoExW(name_w, port_w, .DNS, null, &hints, &res, timeout, null, null, cancel_handle));
11451 // TODO make this append to the queue eagerly rather than blocking until the whole thing finishes
11452 const rc: ws2_32.WinsockError = @enumFromInt(ws2_32.GetAddrInfoExW(name_w, port_w, .DNS, null, &hints, &res, timeout, null, null, null));
1110411453 switch (rc) {
1110511454 @as(ws2_32.WinsockError, @enumFromInt(0)) => break,
11106 .EINTR => continue,
11107 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
11455 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => continue,
1110811456 .NOTINITIALISED => {
1110911457 try initializeWsa(t);
1111011458 continue;
......@@ -11352,29 +11700,33 @@ fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) std.process.SetCurrentD
1135211700 _ = t;
1135311701
1135411702 if (is_windows) {
11355 try Thread.checkCancel();
1135611703 var dir_path_buffer: [windows.PATH_MAX_WIDE]u16 = undefined;
1135711704 // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks
11705 try Thread.checkCancel();
1135811706 const dir_path = try windows.GetFinalPathNameByHandle(dir.handle, .{}, &dir_path_buffer);
1135911707 const path_len_bytes = std.math.cast(u16, dir_path.len * 2) orelse return error.NameTooLong;
11360 try Thread.checkCancel();
1136111708 var nt_name: windows.UNICODE_STRING = .{
1136211709 .Length = path_len_bytes,
1136311710 .MaximumLength = path_len_bytes,
1136411711 .Buffer = @constCast(dir_path.ptr),
1136511712 };
11366 switch (windows.ntdll.RtlSetCurrentDirectory_U(&nt_name)) {
11367 .SUCCESS => return,
11368 .OBJECT_NAME_INVALID => return error.BadPathName,
11369 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
11370 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
11371 .NO_MEDIA_IN_DEVICE => return error.NoDevice,
11372 .INVALID_PARAMETER => |err| return windows.statusBug(err),
11373 .ACCESS_DENIED => return error.AccessDenied,
11374 .OBJECT_PATH_SYNTAX_BAD => |err| return windows.statusBug(err),
11375 .NOT_A_DIRECTORY => return error.NotDir,
11376 else => |status| return windows.unexpectedStatus(status),
11377 }
11713 const syscall: Syscall = try .start();
11714 while (true) switch (windows.ntdll.RtlSetCurrentDirectory_U(&nt_name)) {
11715 .SUCCESS => return syscall.finish(),
11716 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
11717 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
11718 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
11719 .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice),
11720 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),
11721 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
11722 .OBJECT_PATH_SYNTAX_BAD => |err| return syscall.ntstatusBug(err),
11723 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
11724 .CANCELLED => {
11725 try syscall.checkCancel();
11726 continue;
11727 },
11728 else => |status| return syscall.unexpectedNtstatus(status),
11729 };
1137811730 }
1137911731
1138011732 if (dir.handle == posix.AT.FDCWD) return;
......@@ -12185,391 +12537,6 @@ fn initializeWsa(t: *Threaded) error{ NetworkDown, Canceled }!void {
1218512537
1218612538fn 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
1257312540fn scanEnviron(t: *Threaded) void {
1257412541 t.mutex.lock();
1257512542 defer t.mutex.unlock();
......@@ -12688,3 +12655,459 @@ fn scanEnviron(t: *Threaded) void {
1268812655test {
1268912656 _ = @import("Threaded/test.zig");
1269012657}
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;
1139911399
1140011400pub const _ksiginfo = netbsd._ksiginfo;
1140111401pub 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;
1140211405pub const lwpid_t = netbsd.lwpid_t;
1140311406
1140411407pub const lwp_gettid = dragonfly.lwp_gettid;
lib/std/c/netbsd.zig+19-1
......@@ -1,17 +1,35 @@
11const std = @import("../std.zig");
22const clock_t = std.c.clock_t;
3const clockid_t = std.c.clockid_t;
34const pid_t = std.c.pid_t;
45const pthread_t = std.c.pthread_t;
56const sigval_t = std.c.sigval_t;
67const uid_t = std.c.uid_t;
8const timespec = std.c.timespec;
79
810pub extern "c" fn ptrace(request: c_int, pid: pid_t, addr: ?*anyopaque, data: c_int) c_int;
911
1012pub const lwpid_t = i32;
1113
12pub extern "c" fn _lwp_self() lwpid_t;
1314pub 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
1533pub const TCIFLUSH = 1;
1634pub const TCOFLUSH = 2;
1735pub const TCIOFLUSH = 3;
lib/std/debug/SelfInfo/Windows.zig+1-2
......@@ -315,8 +315,7 @@ const Module = struct {
315315 );
316316 if (len == 0) return error.MissingDebugInfo;
317317 const name_w = name_buffer[0 .. len + 4 :0];
318 // TODO eliminate the reference to Io.Threaded.global_single_threaded here
319 const coff_file = Io.Threaded.global_single_threaded.dirOpenFileWtf16(null, name_w, .{}) catch |err| switch (err) {
318 const coff_file = Io.Threaded.dirOpenFileWtf16(null, name_w, .{}) catch |err| switch (err) {
320319 error.Canceled => |e| return e,
321320 error.Unexpected => |e| return e,
322321 error.FileNotFound => return error.MissingDebugInfo,
lib/std/os/windows.zig+6-66
......@@ -2253,7 +2253,7 @@ pub fn GetProcessHeap() ?*HEAP {
22532253pub const OBJECT_ATTRIBUTES = extern struct {
22542254 Length: ULONG,
22552255 RootDirectory: ?HANDLE,
2256 ObjectName: *UNICODE_STRING,
2256 ObjectName: ?*UNICODE_STRING,
22572257 Attributes: ATTRIBUTES,
22582258 SecurityDescriptor: ?*anyopaque,
22592259 SecurityQualityOfService: ?*anyopaque,
......@@ -2306,6 +2306,7 @@ pub const OpenError = error{
23062306 NetworkNotFound,
23072307 AntivirusInterference,
23082308 BadPathName,
2309 OperationCanceled,
23092310};
23102311
23112312pub const OpenFileOptions = struct {
......@@ -2405,6 +2406,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
24052406 continue;
24062407 },
24072408 .VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference,
2409 .CANCELLED => return error.OperationCanceled,
24082410 else => return unexpectedStatus(rc),
24092411 }
24102412 }
......@@ -2985,6 +2987,7 @@ pub const ReadLinkError = error{
29852987 AntivirusInterference,
29862988 UnsupportedReparsePointType,
29872989 NotLink,
2990 OperationCanceled,
29882991};
29892992
29902993/// `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
30153018 const rc = DeviceIoControl(result_handle, FSCTL.GET_REPARSE_POINT, .{ .out = reparse_buf[0..] });
30163019 switch (rc) {
30173020 .SUCCESS => {},
3021 .CANCELLED => return error.OperationCanceled,
30183022 .NOT_A_REPARSE_POINT => return error.NotLink,
30193023 else => return unexpectedStatus(rc),
30203024 }
......@@ -3339,71 +3343,6 @@ pub fn GetStdHandle(handle_id: DWORD) GetStdHandleError!HANDLE {
33393343 return handle;
33403344}
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
34073346pub const QueryObjectNameError = error{
34083347 AccessDenied,
34093348 InvalidHandle,
......@@ -3562,6 +3501,7 @@ pub fn GetFinalPathNameByHandle(
35623501 error.NetworkNotFound => return error.Unexpected,
35633502 error.AntivirusInterference => return error.Unexpected,
35643503 error.BadPathName => return error.Unexpected,
3504 error.OperationCanceled => @panic("TODO: better integrate cancelation"),
35653505 else => |e| return e,
35663506 };
35673507 defer CloseHandle(mgmt_handle);
lib/std/os/windows/ntdll.zig+27
......@@ -554,3 +554,30 @@ pub extern "ntdll" fn RtlWakeConditionVariable(
554554pub extern "ntdll" fn RtlWakeAllConditionVariable(
555555 ConditionVariable: *CONDITION_VARIABLE,
556556) 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 {
11241124 error.NoDevice => return error.Unexpected,
11251125 error.WouldBlock => return error.Unexpected,
11261126 error.AntivirusInterference => return error.Unexpected,
1127 error.OperationCanceled => return error.Unexpected,
11271128 else => |e| return e,
11281129 };
11291130 windows.CloseHandle(sub_dir_handle);
lib/std/process/Child.zig+2-2
......@@ -778,6 +778,7 @@ fn spawnWindows(self: *Child, io: Io) SpawnError!void {
778778 error.WouldBlock => return error.Unexpected, // not possible for "NUL"
779779 error.NetworkNotFound => return error.Unexpected, // not possible for "NUL"
780780 error.AntivirusInterference => return error.Unexpected, // not possible for "NUL"
781 error.OperationCanceled => return error.Unexpected, // we're not canceling the operation
781782 else => |e| return e,
782783 }
783784 else
......@@ -1129,8 +1130,7 @@ fn windowsCreateProcessPathExt(
11291130 defer dir_buf.shrinkRetainingCapacity(dir_path_len);
11301131 const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
11311132 const prefixed_path = try windows.wToPrefixedFileW(null, dir_path_z);
1132 // TODO eliminate this reference
1133 break :dir Io.Threaded.global_single_threaded.dirOpenDirWindows(.cwd(), prefixed_path.span(), .{
1133 break :dir Io.Threaded.dirOpenDirWindows(.cwd(), prefixed_path.span(), .{
11341134 .iterate = true,
11351135 }) catch return error.FileNotFound;
11361136 };