| ... | ... | @@ -105,12 +105,7 @@ pub const Environ = struct { |
| 105 | 105 | }; |
| 106 | 106 | }; |
| 107 | 107 | |
| 108 | | pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum { |
| 109 | | enabled, |
| 110 | | disabled, |
| 111 | | } else enum { |
| 112 | | disabled, |
| 113 | | }; |
| 108 | pub const RobustCancel = enum { enabled, disabled }; |
| 114 | 109 | |
| 115 | 110 | pub const Pid = if (native_os == .linux) enum(posix.pid_t) { |
| 116 | 111 | unknown = 0, |
| ... | ... | @@ -514,13 +509,21 @@ const AwaitableId = enum(@Int(.unsigned, @bitSizeOf(usize) - 3)) { |
| 514 | 509 | |
| 515 | 510 | const Thread = struct { |
| 516 | 511 | 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, |
| 520 | 515 | |
| 521 | 516 | status: std.atomic.Value(Status), |
| 522 | 517 | |
| 523 | 518 | cancel_protection: Io.CancelProtection, |
| 519 | /// Always released when `Status.cancelation` is set to `.parked`. |
| 520 | futex_waiter: if (use_parking_futex) ?*parking_futex.Waiter else ?noreturn, |
| 521 | |
| 522 | const Handle = Handle: { |
| 523 | if (std.Thread.use_pthreads) break :Handle std.c.pthread_t; |
| 524 | if (builtin.target.os.tag == .windows) break :Handle windows.HANDLE; |
| 525 | break :Handle void; |
| 526 | }; |
| 524 | 527 | |
| 525 | 528 | const Status = packed struct(usize) { |
| 526 | 529 | /// The specific values of these enum fields are chosen to simplify the implementation of |
| ... | ... | @@ -531,7 +534,7 @@ const Thread = struct { |
| 531 | 534 | none = 0b000, |
| 532 | 535 | |
| 533 | 536 | /// The thread is parked in a cancelable futex wait or sleep. |
| 534 | | /// Only applicable on Windows, NetBSD, and Illumos. |
| 537 | /// Only applicable if `use_parking_futex` or `use_parking_sleep`. |
| 535 | 538 | /// To request cancelation, set the status to `.canceling` and unpark the thread. |
| 536 | 539 | /// To unpark for another reason (futex wake), set the status to `.none` and unpark the thread. |
| 537 | 540 | parked = 0b001, |
| ... | ... | @@ -540,8 +543,8 @@ const Thread = struct { |
| 540 | 543 | /// To request cancelation, set the status to `.blocked_canceling` and repeatedly interrupt the system call until the status changes. |
| 541 | 544 | blocked = 0b011, |
| 542 | 545 | |
| 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`. |
| 545 | 548 | blocked_windows_dns = 0b010, |
| 546 | 549 | |
| 547 | 550 | /// The thread has an outstanding cancelation request but is not in a cancelable operation. |
| ... | ... | @@ -597,10 +600,6 @@ const Thread = struct { |
| 597 | 600 | } |
| 598 | 601 | } |
| 599 | 602 | |
| 600 | | fn currentSignaleeId() SignaleeId { |
| 601 | | return if (std.Thread.use_pthreads) std.c.pthread_self() else std.Thread.getCurrentId(); |
| 602 | | } |
| 603 | | |
| 604 | 603 | fn futexWaitUncancelable(ptr: *const u32, expect: u32, timeout_ns: ?u64) void { |
| 605 | 604 | return Thread.futexWaitInner(ptr, expect, true, timeout_ns) catch unreachable; |
| 606 | 605 | } |
| ... | ... | @@ -614,8 +613,19 @@ const Thread = struct { |
| 614 | 613 | |
| 615 | 614 | if (builtin.single_threaded) unreachable; // nobody would ever wake us |
| 616 | 615 | |
| 617 | | if (builtin.cpu.arch.isWasm()) { |
| 616 | if (use_parking_futex) { |
| 617 | return parking_futex.wait( |
| 618 | ptr, |
| 619 | expect, |
| 620 | uncancelable, |
| 621 | if (timeout_ns) |ns| .{ .duration = .{ |
| 622 | .raw = .fromNanoseconds(ns), |
| 623 | .clock = .boot, |
| 624 | } } else .none, |
| 625 | ); |
| 626 | } else if (builtin.cpu.arch.isWasm()) { |
| 618 | 627 | comptime assert(builtin.cpu.has(.wasm, .atomics)); |
| 628 | // TODO implement cancelation for WASM futex waits by signaling the futex |
| 619 | 629 | if (!uncancelable) try Thread.checkCancel(); |
| 620 | 630 | const to: i64 = if (timeout_ns) |ns| ns else -1; |
| 621 | 631 | const signed_expect: i32 = @bitCast(expect); |
| ... | ... | @@ -689,24 +699,6 @@ const Thread = struct { |
| 689 | 699 | else => recoverableOsBugDetected(), |
| 690 | 700 | } |
| 691 | 701 | }, |
| 692 | | .windows => { |
| 693 | | var timeout_value: windows.LARGE_INTEGER = undefined; |
| 694 | | var timeout_ptr: ?*const windows.LARGE_INTEGER = null; |
| 695 | | // NTDLL functions work with time in units of 100 nanoseconds. |
| 696 | | // Positive values are absolute deadlines while negative values are relative durations. |
| 697 | | if (timeout_ns) |delay| { |
| 698 | | timeout_value = @as(windows.LARGE_INTEGER, @intCast(delay / 100)); |
| 699 | | timeout_value = -timeout_value; |
| 700 | | timeout_ptr = &timeout_value; |
| 701 | | } |
| 702 | | if (!uncancelable) try Thread.checkCancel(); |
| 703 | | switch (windows.ntdll.RtlWaitOnAddress(ptr, &expect, @sizeOf(@TypeOf(expect)), timeout_ptr)) { |
| 704 | | .SUCCESS => {}, |
| 705 | | .CANCELLED => {}, |
| 706 | | .TIMEOUT => {}, // timeout |
| 707 | | else => recoverableOsBugDetected(), |
| 708 | | } |
| 709 | | }, |
| 710 | 702 | .freebsd => { |
| 711 | 703 | const flags = @intFromEnum(std.c.UMTX_OP.WAIT_UINT_PRIVATE); |
| 712 | 704 | var tm_size: usize = 0; |
| ... | ... | @@ -738,7 +730,7 @@ const Thread = struct { |
| 738 | 730 | tm_ptr = &tm; |
| 739 | 731 | tm = timestampToPosix(ns); |
| 740 | 732 | } |
| 741 | | if (thread) |t| try t.beginSyscall(); |
| 733 | const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start(); |
| 742 | 734 | const rc = std.c.futex( |
| 743 | 735 | ptr, |
| 744 | 736 | std.c.FUTEX.WAIT | std.c.FUTEX.PRIVATE_FLAG, |
| ... | ... | @@ -746,7 +738,7 @@ const Thread = struct { |
| 746 | 738 | tm_ptr, |
| 747 | 739 | null, // uaddr2 is ignored |
| 748 | 740 | ); |
| 749 | | if (thread) |t| t.endSyscall(); |
| 741 | syscall.finish(); |
| 750 | 742 | if (is_debug) switch (posix.errno(rc)) { |
| 751 | 743 | .SUCCESS => {}, |
| 752 | 744 | .NOSYS => unreachable, // constant op known good value |
| ... | ... | @@ -765,9 +757,9 @@ const Thread = struct { |
| 765 | 757 | } else { |
| 766 | 758 | timeout_us = 0; |
| 767 | 759 | } |
| 768 | | if (thread) |t| try t.beginSyscall(); |
| 760 | const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start(); |
| 769 | 761 | const rc = std.c.umtx_sleep(@ptrCast(ptr), @bitCast(expect), timeout_us); |
| 770 | | if (thread) |t| t.endSyscall(); |
| 762 | syscall.finish(); |
| 771 | 763 | if (is_debug) switch (std.posix.errno(rc)) { |
| 772 | 764 | .SUCCESS => {}, |
| 773 | 765 | .BUSY => {}, // ptr != expect |
| ... | ... | @@ -777,14 +769,7 @@ const Thread = struct { |
| 777 | 769 | else => unreachable, |
| 778 | 770 | }; |
| 779 | 771 | }, |
| 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"), |
| 788 | 773 | } |
| 789 | 774 | } |
| 790 | 775 | |
| ... | ... | @@ -794,7 +779,9 @@ const Thread = struct { |
| 794 | 779 | |
| 795 | 780 | if (builtin.single_threaded) return; // nothing to wake up |
| 796 | 781 | |
| 797 | | if (builtin.cpu.arch.isWasm()) { |
| 782 | if (use_parking_futex) { |
| 783 | return parking_futex.wake(ptr, max_waiters); |
| 784 | } else if (builtin.cpu.arch.isWasm()) { |
| 798 | 785 | comptime assert(builtin.cpu.has(.wasm, .atomics)); |
| 799 | 786 | const woken_count = asm volatile ( |
| 800 | 787 | \\local.get %[ptr] |
| ... | ... | @@ -839,12 +826,6 @@ const Thread = struct { |
| 839 | 826 | } |
| 840 | 827 | } |
| 841 | 828 | }, |
| 842 | | .windows => { |
| 843 | | switch (max_waiters) { |
| 844 | | 1 => windows.ntdll.RtlWakeAddressSingle(ptr), |
| 845 | | else => windows.ntdll.RtlWakeAddressAll(ptr), |
| 846 | | } |
| 847 | | }, |
| 848 | 829 | .freebsd => { |
| 849 | 830 | const rc = std.c._umtx_op( |
| 850 | 831 | @intFromPtr(ptr), |
| ... | ... | @@ -877,11 +858,7 @@ const Thread = struct { |
| 877 | 858 | @min(max_waiters, std.math.maxInt(c_int)), |
| 878 | 859 | ); |
| 879 | 860 | }, |
| 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"), |
| 885 | 862 | } |
| 886 | 863 | } |
| 887 | 864 | |
| ... | ... | @@ -905,10 +882,14 @@ const Thread = struct { |
| 905 | 882 | .parked => thread.status.cmpxchgWeak( |
| 906 | 883 | .{ .cancelation = .parked, .awaitable = awaitable }, |
| 907 | 884 | .{ .cancelation = .canceling, .awaitable = awaitable }, |
| 908 | | .monotonic, |
| 885 | .acquire, // acquire `thread.futex_waiter` |
| 909 | 886 | .monotonic, |
| 910 | 887 | ) orelse { |
| 911 | | if (true) @panic("MLUGG TODO: unpark thread"); |
| 888 | if (!use_parking_futex and !use_parking_sleep) unreachable; |
| 889 | if (thread.futex_waiter) |futex_waiter| { |
| 890 | parking_futex.removeCanceledWaiter(futex_waiter); |
| 891 | } |
| 892 | unpark(&.{thread.id}, null); |
| 912 | 893 | return false; |
| 913 | 894 | }, |
| 914 | 895 | |
| ... | ... | @@ -924,7 +905,15 @@ const Thread = struct { |
| 924 | 905 | .{ .cancelation = .canceling, .awaitable = awaitable }, |
| 925 | 906 | .monotonic, |
| 926 | 907 | .monotonic, |
| 927 | | ) orelse return false, |
| 908 | ) orelse { |
| 909 | if (builtin.target.os.tag != .windows) unreachable; |
| 910 | if (true) { |
| 911 | // TODO: cancel Windows DNS queries. This code path is currently impossible |
| 912 | // as `netLookupFallible` doesn't actually use `.blocked_windows_dns` yet. |
| 913 | unreachable; |
| 914 | } |
| 915 | return false; |
| 916 | }, |
| 928 | 917 | |
| 929 | 918 | .canceling, .canceled => { |
| 930 | 919 | // This can happen when the task start raced with the cancelation, so the thread |
| ... | ... | @@ -951,26 +940,38 @@ const Thread = struct { |
| 951 | 940 | const bad_status: Status = .{ .cancelation = .blocked_canceling, .awaitable = awaitable }; |
| 952 | 941 | if (thread.status.load(.monotonic) != bad_status) return false; |
| 953 | 942 | |
| 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. |
| 957 | 945 | |
| 958 | 946 | 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, |
| 967 | 950 | }; |
| 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, |
| 971 | 974 | } |
| 972 | | |
| 973 | | return true; |
| 974 | 975 | } |
| 975 | 976 | |
| 976 | 977 | /// Like a `*Thread`, but 2 bits smaller than a pointer (because the LSBs are always 0 due to |
| ... | ... | @@ -1069,6 +1070,18 @@ const Syscall = struct { |
| 1069 | 1070 | s.finish(); |
| 1070 | 1071 | return posix.unexpectedErrno(err); |
| 1071 | 1072 | } |
| 1073 | /// Convenience wrapper which calls `finish`, then calls `windows.statusBug`. |
| 1074 | fn ntstatusBug(s: Syscall, status: windows.NTSTATUS) Io.UnexpectedError { |
| 1075 | @branchHint(.cold); |
| 1076 | s.finish(); |
| 1077 | return windows.statusBug(status); |
| 1078 | } |
| 1079 | /// Convenience wrapper which calls `finish`, then calls `windows.unexpectedStatus`. |
| 1080 | fn unexpectedNtstatus(s: Syscall, status: windows.NTSTATUS) Io.UnexpectedError { |
| 1081 | @branchHint(.cold); |
| 1082 | s.finish(); |
| 1083 | return windows.unexpectedStatus(status); |
| 1084 | } |
| 1072 | 1085 | }; |
| 1073 | 1086 | |
| 1074 | 1087 | const max_iovecs_len = 8; |
| ... | ... | @@ -1233,15 +1246,45 @@ fn join(t: *Threaded) void { |
| 1233 | 1246 | fn worker(t: *Threaded) void { |
| 1234 | 1247 | var thread: Thread = .{ |
| 1235 | 1248 | .next = undefined, |
| 1236 | | .signalee_id = Thread.currentSignaleeId(), |
| 1249 | .id = std.Thread.getCurrentId(), |
| 1250 | .handle = handle: { |
| 1251 | if (std.Thread.use_pthreads) break :handle std.c.pthread_self(); |
| 1252 | if (builtin.target.os.tag == .windows) break :handle undefined; // populated below |
| 1253 | }, |
| 1237 | 1254 | .status = .init(.{ |
| 1238 | 1255 | .cancelation = .none, |
| 1239 | 1256 | .awaitable = .null, |
| 1240 | 1257 | }), |
| 1241 | 1258 | .cancel_protection = .unblocked, |
| 1259 | .futex_waiter = undefined, |
| 1242 | 1260 | }; |
| 1243 | 1261 | Thread.current = &thread; |
| 1244 | 1262 | |
| 1263 | if (builtin.target.os.tag == .windows) { |
| 1264 | assert(windows.ntdll.NtOpenThread( |
| 1265 | &thread.handle, |
| 1266 | .{ |
| 1267 | .SPECIFIC = .{ |
| 1268 | .THREAD = .{ |
| 1269 | .TERMINATE = true, // for `NtCancelSynchronousIoFile` |
| 1270 | }, |
| 1271 | }, |
| 1272 | }, |
| 1273 | &.{ |
| 1274 | .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), |
| 1275 | .RootDirectory = null, |
| 1276 | .ObjectName = null, |
| 1277 | .Attributes = .{}, |
| 1278 | .SecurityDescriptor = null, |
| 1279 | .SecurityQualityOfService = null, |
| 1280 | }, |
| 1281 | &windows.teb().ClientId, |
| 1282 | ) == .SUCCESS); |
| 1283 | } |
| 1284 | defer if (builtin.target.os.tag == .windows) { |
| 1285 | windows.CloseHandle(thread.handle); |
| 1286 | }; |
| 1287 | |
| 1245 | 1288 | { |
| 1246 | 1289 | var head = t.worker_threads.load(.monotonic); |
| 1247 | 1290 | while (true) { |
| ... | ... | @@ -2127,26 +2170,34 @@ fn dirCreateDirWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permi |
| 2127 | 2170 | fn dirCreateDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void { |
| 2128 | 2171 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2129 | 2172 | _ = t; |
| 2130 | | try Thread.checkCancel(); |
| 2131 | 2173 | |
| 2132 | 2174 | const sub_path_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path); |
| 2133 | 2175 | _ = 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 | }; |
| 2149 | 2199 | }; |
| 2200 | syscall.finish(); |
| 2150 | 2201 | windows.CloseHandle(sub_dir_handle); |
| 2151 | 2202 | } |
| 2152 | 2203 | |
| ... | ... | @@ -2225,9 +2276,7 @@ fn dirCreateDirPathOpenWindows( |
| 2225 | 2276 | .path = sub_path, |
| 2226 | 2277 | }; |
| 2227 | 2278 | |
| 2228 | | while (true) { |
| 2229 | | try Thread.checkCancel(); |
| 2230 | | |
| 2279 | components: while (true) { |
| 2231 | 2280 | const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, component.path); |
| 2232 | 2281 | const sub_path_w = sub_path_w_array.span(); |
| 2233 | 2282 | const is_last = it.peekNext() == null; |
| ... | ... | @@ -2242,7 +2291,9 @@ fn dirCreateDirPathOpenWindows( |
| 2242 | 2291 | .Buffer = @constCast(sub_path_w.ptr), |
| 2243 | 2292 | }; |
| 2244 | 2293 | var io_status_block: w.IO_STATUS_BLOCK = undefined; |
| 2245 | | const rc = w.ntdll.NtCreateFile( |
| 2294 | |
| 2295 | const syscall: Syscall = try .start(); |
| 2296 | while (true) switch (w.ntdll.NtCreateFile( |
| 2246 | 2297 | &result.handle, |
| 2247 | 2298 | .{ |
| 2248 | 2299 | .SPECIFIC = .{ .FILE_DIRECTORY = .{ |
| ... | ... | @@ -2277,16 +2328,20 @@ fn dirCreateDirPathOpenWindows( |
| 2277 | 2328 | }, |
| 2278 | 2329 | null, |
| 2279 | 2330 | 0, |
| 2280 | | ); |
| 2281 | | |
| 2282 | | switch (rc) { |
| 2331 | )) { |
| 2283 | 2332 | .SUCCESS => { |
| 2333 | syscall.finish(); |
| 2284 | 2334 | component = it.next() orelse return result; |
| 2285 | 2335 | w.CloseHandle(result.handle); |
| 2336 | continue :components; |
| 2337 | }, |
| 2338 | .CANCELLED => { |
| 2339 | try syscall.checkCancel(); |
| 2286 | 2340 | continue; |
| 2287 | 2341 | }, |
| 2288 | | .OBJECT_NAME_INVALID => return error.BadPathName, |
| 2342 | .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), |
| 2289 | 2343 | .OBJECT_NAME_COLLISION => { |
| 2344 | syscall.finish(); |
| 2290 | 2345 | assert(!is_last); |
| 2291 | 2346 | // stat the file and return an error if it's not a directory |
| 2292 | 2347 | // this is important because otherwise a dangling symlink |
| ... | ... | @@ -2297,23 +2352,24 @@ fn dirCreateDirPathOpenWindows( |
| 2297 | 2352 | if (fstat.kind != .directory) return error.NotDir; |
| 2298 | 2353 | |
| 2299 | 2354 | component = it.next().?; |
| 2300 | | continue; |
| 2355 | continue :components; |
| 2301 | 2356 | }, |
| 2302 | 2357 | |
| 2303 | 2358 | .OBJECT_NAME_NOT_FOUND, |
| 2304 | 2359 | .OBJECT_PATH_NOT_FOUND, |
| 2305 | 2360 | => { |
| 2361 | syscall.finish(); |
| 2306 | 2362 | component = it.previous() orelse return error.FileNotFound; |
| 2307 | | continue; |
| 2363 | continue :components; |
| 2308 | 2364 | }, |
| 2309 | 2365 | |
| 2310 | | .NOT_A_DIRECTORY => return error.NotDir, |
| 2366 | .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), |
| 2311 | 2367 | // This can happen if the directory has 'List folder contents' permission set to 'Deny' |
| 2312 | 2368 | // 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 | }; |
| 2317 | 2373 | } |
| 2318 | 2374 | } |
| 2319 | 2375 | |
| ... | ... | @@ -2637,20 +2693,31 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { |
| 2637 | 2693 | fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { |
| 2638 | 2694 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2639 | 2695 | _ = t; |
| 2640 | | try Thread.checkCancel(); |
| 2641 | 2696 | |
| 2642 | 2697 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 2643 | 2698 | var info: windows.FILE.ALL_INFORMATION = undefined; |
| 2644 | | const rc = windows.ntdll.NtQueryInformationFile(file.handle, &io_status_block, &info, @sizeOf(windows.FILE.ALL_INFORMATION), .All); |
| 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 | }; |
| 2654 | 2721 | } |
| 2655 | 2722 | return .{ |
| 2656 | 2723 | .inode = info.InternalInformation.IndexNumber, |
| ... | ... | @@ -2658,15 +2725,25 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { |
| 2658 | 2725 | .permissions = .default_file, |
| 2659 | 2726 | .kind = if (info.BasicInformation.FileAttributes.REPARSE_POINT) reparse_point: { |
| 2660 | 2727 | var tag_info: windows.FILE.ATTRIBUTE_TAG_INFO = undefined; |
| 2661 | | const tag_rc = windows.ntdll.NtQueryInformationFile(file.handle, &io_status_block, &tag_info, @sizeOf(windows.FILE.ATTRIBUTE_TAG_INFO), .AttributeTag); |
| 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(), |
| 2664 | 2737 | // INFO_LENGTH_MISMATCH and ACCESS_DENIED are the only documented possible errors |
| 2665 | 2738 | // 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 | }; |
| 2670 | 2747 | if (tag_info.ReparseTag.IsSurrogate) break :reparse_point .sym_link; |
| 2671 | 2748 | // Unknown reparse point |
| 2672 | 2749 | break :reparse_point .unknown; |
| ... | ... | @@ -2853,7 +2930,6 @@ fn dirAccessWindows( |
| 2853 | 2930 | ) Dir.AccessError!void { |
| 2854 | 2931 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2855 | 2932 | _ = t; |
| 2856 | | try Thread.checkCancel(); |
| 2857 | 2933 | |
| 2858 | 2934 | _ = options; // TODO |
| 2859 | 2935 | |
| ... | ... | @@ -2879,16 +2955,21 @@ fn dirAccessWindows( |
| 2879 | 2955 | .SecurityQualityOfService = null, |
| 2880 | 2956 | }; |
| 2881 | 2957 | 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 | }; |
| 2892 | 2973 | } |
| 2893 | 2974 | |
| 2894 | 2975 | const dirCreateFile = switch (native_os) { |
| ... | ... | @@ -3071,27 +3152,40 @@ fn dirCreateFileWindows( |
| 3071 | 3152 | const w = windows; |
| 3072 | 3153 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 3073 | 3154 | _ = t; |
| 3074 | | try Thread.checkCancel(); |
| 3075 | 3155 | |
| 3076 | 3156 | const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, sub_path); |
| 3077 | 3157 | const sub_path_w = sub_path_w_array.span(); |
| 3078 | 3158 | |
| 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 | }; |
| 3095 | 3189 | errdefer w.CloseHandle(handle); |
| 3096 | 3190 | |
| 3097 | 3191 | var io_status_block: w.IO_STATUS_BLOCK = undefined; |
| ... | ... | @@ -3100,7 +3194,8 @@ fn dirCreateFileWindows( |
| 3100 | 3194 | .shared => false, |
| 3101 | 3195 | .exclusive => true, |
| 3102 | 3196 | }; |
| 3103 | | const status = w.ntdll.NtLockFile( |
| 3197 | const syscall: Syscall = try .start(); |
| 3198 | while (true) switch (w.ntdll.NtLockFile( |
| 3104 | 3199 | handle, |
| 3105 | 3200 | null, |
| 3106 | 3201 | null, |
| ... | ... | @@ -3111,16 +3206,16 @@ fn dirCreateFileWindows( |
| 3111 | 3206 | null, |
| 3112 | 3207 | @intFromBool(flags.lock_nonblocking), |
| 3113 | 3208 | @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 | }; |
| 3124 | 3219 | } |
| 3125 | 3220 | |
| 3126 | 3221 | fn dirCreateFileWasi( |
| ... | ... | @@ -3399,14 +3494,14 @@ fn dirOpenFileWindows( |
| 3399 | 3494 | flags: File.OpenFlags, |
| 3400 | 3495 | ) File.OpenError!File { |
| 3401 | 3496 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 3497 | _ = t; |
| 3402 | 3498 | const sub_path_w_array = try windows.sliceToPrefixedFileW(dir.handle, sub_path); |
| 3403 | 3499 | const sub_path_w = sub_path_w_array.span(); |
| 3404 | 3500 | const dir_handle = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle; |
| 3405 | | return dirOpenFileWtf16(t, dir_handle, sub_path_w, flags); |
| 3501 | return dirOpenFileWtf16(dir_handle, sub_path_w, flags); |
| 3406 | 3502 | } |
| 3407 | 3503 | |
| 3408 | 3504 | pub fn dirOpenFileWtf16( |
| 3409 | | t: *Threaded, |
| 3410 | 3505 | dir_handle: ?windows.HANDLE, |
| 3411 | 3506 | sub_path_w: [:0]const u16, |
| 3412 | 3507 | flags: File.OpenFlags, |
| ... | ... | @@ -3415,7 +3510,6 @@ pub fn dirOpenFileWtf16( |
| 3415 | 3510 | if (!allow_directory and std.mem.eql(u16, sub_path_w, &.{'.'})) return error.IsDir; |
| 3416 | 3511 | if (!allow_directory and std.mem.eql(u16, sub_path_w, &.{ '.', '.' })) return error.IsDir; |
| 3417 | 3512 | const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong; |
| 3418 | | _ = t; |
| 3419 | 3513 | const w = windows; |
| 3420 | 3514 | |
| 3421 | 3515 | var nt_name: w.UNICODE_STRING = .{ |
| ... | ... | @@ -3437,11 +3531,10 @@ pub fn dirOpenFileWtf16( |
| 3437 | 3531 | const max_attempts = 13; |
| 3438 | 3532 | var attempt: u5 = 0; |
| 3439 | 3533 | |
| 3534 | var syscall: Syscall = try .start(); |
| 3440 | 3535 | const handle = while (true) { |
| 3441 | | try Thread.checkCancel(); |
| 3442 | | |
| 3443 | 3536 | var result: w.HANDLE = undefined; |
| 3444 | | const rc = w.ntdll.NtCreateFile( |
| 3537 | switch (w.ntdll.NtCreateFile( |
| 3445 | 3538 | &result, |
| 3446 | 3539 | .{ |
| 3447 | 3540 | .STANDARD = .{ .SYNCHRONIZE = true }, |
| ... | ... | @@ -3463,49 +3556,59 @@ pub fn dirOpenFileWtf16( |
| 3463 | 3556 | }, |
| 3464 | 3557 | null, |
| 3465 | 3558 | 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 | }, |
| 3476 | 3575 | .SHARING_VIOLATION => { |
| 3477 | 3576 | // This occurs if the file attempting to be opened is a running |
| 3478 | 3577 | // executable. However, there's a kernel bug: the error may be |
| 3479 | 3578 | // incorrectly returned for an indeterminate amount of time |
| 3480 | 3579 | // after an executable file is closed. Here we work around the |
| 3481 | 3580 | // kernel bug with retry attempts. |
| 3581 | syscall.finish(); |
| 3482 | 3582 | if (max_attempts - attempt == 0) return error.SharingViolation; |
| 3483 | 3583 | _ = w.kernel32.SleepEx((@as(u32, 1) << attempt) >> 1, w.TRUE); |
| 3484 | 3584 | attempt += 1; |
| 3585 | syscall = try .start(); |
| 3485 | 3586 | continue; |
| 3486 | 3587 | }, |
| 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), |
| 3496 | 3597 | .DELETE_PENDING => { |
| 3497 | 3598 | // This error means that there *was* a file in this location on |
| 3498 | 3599 | // the file system, but it was deleted. However, the OS is not |
| 3499 | 3600 | // finished with the deletion operation, and so this CreateFile |
| 3500 | 3601 | // call has failed. Here, we simulate the kernel bug being |
| 3501 | 3602 | // fixed by sleeping and retrying until the error goes away. |
| 3603 | syscall.finish(); |
| 3502 | 3604 | if (max_attempts - attempt == 0) return error.SharingViolation; |
| 3503 | 3605 | _ = w.kernel32.SleepEx((@as(u32, 1) << attempt) >> 1, w.TRUE); |
| 3504 | 3606 | attempt += 1; |
| 3607 | syscall = try .start(); |
| 3505 | 3608 | continue; |
| 3506 | 3609 | }, |
| 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), |
| 3509 | 3612 | } |
| 3510 | 3613 | }; |
| 3511 | 3614 | errdefer w.CloseHandle(handle); |
| ... | ... | @@ -3515,7 +3618,8 @@ pub fn dirOpenFileWtf16( |
| 3515 | 3618 | .shared => false, |
| 3516 | 3619 | .exclusive => true, |
| 3517 | 3620 | }; |
| 3518 | | const status = w.ntdll.NtLockFile( |
| 3621 | syscall = try .start(); |
| 3622 | while (true) switch (w.ntdll.NtLockFile( |
| 3519 | 3623 | handle, |
| 3520 | 3624 | null, |
| 3521 | 3625 | null, |
| ... | ... | @@ -3526,14 +3630,13 @@ pub fn dirOpenFileWtf16( |
| 3526 | 3630 | null, |
| 3527 | 3631 | @intFromBool(flags.lock_nonblocking), |
| 3528 | 3632 | @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 | }; |
| 3537 | 3640 | return .{ .handle = handle }; |
| 3538 | 3641 | } |
| 3539 | 3642 | |
| ... | ... | @@ -3773,8 +3876,9 @@ pub fn dirOpenDirWindows( |
| 3773 | 3876 | }; |
| 3774 | 3877 | var io_status_block: w.IO_STATUS_BLOCK = undefined; |
| 3775 | 3878 | 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( |
| 3778 | 3882 | &result.handle, |
| 3779 | 3883 | // TODO remove some of these flags if options.access_sub_paths is false |
| 3780 | 3884 | .{ |
| ... | ... | @@ -3810,21 +3914,26 @@ pub fn dirOpenDirWindows( |
| 3810 | 3914 | }, |
| 3811 | 3915 | null, |
| 3812 | 3916 | 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), |
| 3819 | 3928 | .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), |
| 3822 | 3931 | // This can happen if the directory has 'List folder contents' permission set to 'Deny' |
| 3823 | 3932 | // 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 | }; |
| 3828 | 3937 | } |
| 3829 | 3938 | |
| 3830 | 3939 | fn dirClose(userdata: ?*anyopaque, dirs: []const Dir) void { |
| ... | ... | @@ -4264,9 +4373,9 @@ fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) D |
| 4264 | 4373 | // buffered data. |
| 4265 | 4374 | if (buffer_index != 0) break; |
| 4266 | 4375 | |
| 4267 | | try Thread.checkCancel(); |
| 4268 | 4376 | var io_status_block: w.IO_STATUS_BLOCK = undefined; |
| 4269 | | const rc = w.ntdll.NtQueryDirectoryFile( |
| 4377 | const syscall: Syscall = try .start(); |
| 4378 | const rc = while (true) switch (w.ntdll.NtQueryDirectoryFile( |
| 4270 | 4379 | dr.dir.handle, |
| 4271 | 4380 | null, |
| 4272 | 4381 | null, |
| ... | ... | @@ -4278,7 +4387,16 @@ fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) D |
| 4278 | 4387 | w.FALSE, |
| 4279 | 4388 | null, |
| 4280 | 4389 | @intFromBool(dr.state == .reset), |
| 4281 | | ); |
| 4390 | )) { |
| 4391 | .CANCELLED => { |
| 4392 | try syscall.checkCancel(); |
| 4393 | continue; |
| 4394 | }, |
| 4395 | else => |rc| { |
| 4396 | syscall.finish(); |
| 4397 | break rc; |
| 4398 | }, |
| 4399 | }; |
| 4282 | 4400 | dr.state = .reading; |
| 4283 | 4401 | if (io_status_block.Information == 0) { |
| 4284 | 4402 | dr.state = .finished; |
| ... | ... | @@ -4466,32 +4584,40 @@ fn dirRealPathFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, |
| 4466 | 4584 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 4467 | 4585 | _ = t; |
| 4468 | 4586 | |
| 4469 | | try Thread.checkCancel(); |
| 4470 | | |
| 4471 | 4587 | var path_name_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path); |
| 4472 | 4588 | |
| 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 | } |
| 4487 | 4612 | }; |
| 4488 | 4613 | defer windows.CloseHandle(h_file); |
| 4489 | 4614 | return realPathWindows(h_file, out_buffer); |
| 4490 | 4615 | } |
| 4491 | 4616 | |
| 4492 | 4617 | fn realPathWindows(h_file: windows.HANDLE, out_buffer: []u8) File.RealPathError!usize { |
| 4493 | | // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks |
| 4494 | 4618 | var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; |
| 4619 | // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks |
| 4620 | try Thread.checkCancel(); |
| 4495 | 4621 | const wide_slice = try windows.GetFinalPathNameByHandle(h_file, .{}, &wide_buf); |
| 4496 | 4622 | |
| 4497 | 4623 | const len = std.unicode.calcWtf8Len(wide_slice); |
| ... | ... | @@ -4885,8 +5011,6 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov |
| 4885 | 5011 | _ = t; |
| 4886 | 5012 | const w = windows; |
| 4887 | 5013 | |
| 4888 | | try Thread.checkCancel(); |
| 4889 | | |
| 4890 | 5014 | const sub_path_w_buf = try w.sliceToPrefixedFileW(dir.handle, sub_path); |
| 4891 | 5015 | const sub_path_w = sub_path_w_buf.span(); |
| 4892 | 5016 | |
| ... | ... | @@ -4909,47 +5033,49 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov |
| 4909 | 5033 | |
| 4910 | 5034 | var io_status_block: w.IO_STATUS_BLOCK = undefined; |
| 4911 | 5035 | 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 | }; |
| 4953 | 5079 | } |
| 4954 | 5080 | defer w.CloseHandle(tmp_handle); |
| 4955 | 5081 | |
| ... | ... | @@ -4964,9 +5090,7 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov |
| 4964 | 5090 | // |
| 4965 | 5091 | // The strategy here is just to try using FileDispositionInformationEx and fall back to |
| 4966 | 5092 | // 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: { |
| 4970 | 5094 | // Deletion with posix semantics if the filesystem supports it. |
| 4971 | 5095 | const info: w.FILE.DISPOSITION.INFORMATION.EX = .{ .Flags = .{ |
| 4972 | 5096 | .DELETE = true, |
| ... | ... | @@ -4974,29 +5098,32 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov |
| 4974 | 5098 | .IGNORE_READONLY_ATTRIBUTE = true, |
| 4975 | 5099 | } }; |
| 4976 | 5100 | |
| 4977 | | rc = w.ntdll.NtSetInformationFile( |
| 5101 | const syscall: Syscall = try .start(); |
| 5102 | while (true) switch (w.ntdll.NtSetInformationFile( |
| 4978 | 5103 | tmp_handle, |
| 4979 | 5104 | &io_status_block, |
| 4980 | 5105 | &info, |
| 4981 | 5106 | @sizeOf(w.FILE.DISPOSITION.INFORMATION.EX), |
| 4982 | 5107 | .DispositionEx, |
| 4983 | | ); |
| 4984 | | switch (rc) { |
| 4985 | | .SUCCESS => return, |
| 5108 | )) { |
| 5109 | .CANCELLED => { |
| 5110 | try syscall.checkCancel(); |
| 5111 | continue; |
| 5112 | }, |
| 4986 | 5113 | // The filesystem does not support FileDispositionInformationEx |
| 4987 | 5114 | .INVALID_PARAMETER, |
| 4988 | 5115 | // The operating system does not support FileDispositionInformationEx |
| 4989 | 5116 | .INVALID_INFO_CLASS, |
| 4990 | 5117 | // The operating system does not support one of the flags |
| 4991 | 5118 | .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 |
| 4997 | 5120 | |
| 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 | }; |
| 5000 | 5127 | |
| 5001 | 5128 | // Deletion with file pending semantics, which requires waiting or moving |
| 5002 | 5129 | // files to get them removed (from here). |
| ... | ... | @@ -5004,14 +5131,23 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov |
| 5004 | 5131 | .DeleteFile = w.TRUE, |
| 5005 | 5132 | }; |
| 5006 | 5133 | |
| 5007 | | rc = w.ntdll.NtSetInformationFile( |
| 5134 | while (true) switch (w.ntdll.NtSetInformationFile( |
| 5008 | 5135 | tmp_handle, |
| 5009 | 5136 | &io_status_block, |
| 5010 | 5137 | &file_dispo, |
| 5011 | 5138 | @sizeOf(w.FILE.DISPOSITION.INFORMATION), |
| 5012 | 5139 | .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 | }; |
| 5015 | 5151 | switch (rc) { |
| 5016 | 5152 | .SUCCESS => {}, |
| 5017 | 5153 | .DIRECTORY_NOT_EMPTY => return error.DirNotEmpty, |
| ... | ... | @@ -5135,23 +5271,33 @@ fn dirRenameWindows( |
| 5135 | 5271 | const new_path_w = new_path_w_buf.span(); |
| 5136 | 5272 | const replace_if_exists = true; |
| 5137 | 5273 | |
| 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 | } |
| 5155 | 5301 | }; |
| 5156 | 5302 | defer w.CloseHandle(src_fd); |
| 5157 | 5303 | |
| ... | ... | @@ -5354,8 +5500,6 @@ fn dirSymLinkWindows( |
| 5354 | 5500 | _ = t; |
| 5355 | 5501 | const w = windows; |
| 5356 | 5502 | |
| 5357 | | try Thread.checkCancel(); |
| 5358 | | |
| 5359 | 5503 | // Target path does not use sliceToPrefixedFileW because certain paths |
| 5360 | 5504 | // are handled differently when creating a symlink than they would be |
| 5361 | 5505 | // when converting to an NT namespaced path. CreateSymbolicLink in |
| ... | ... | @@ -5385,22 +5529,34 @@ fn dirSymLinkWindows( |
| 5385 | 5529 | Flags: w.ULONG, |
| 5386 | 5530 | }; |
| 5387 | 5531 | |
| 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 | } |
| 5404 | 5560 | }; |
| 5405 | 5561 | defer w.CloseHandle(symlink_handle); |
| 5406 | 5562 | |
| ... | ... | @@ -5576,11 +5732,21 @@ fn dirReadLinkWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buf |
| 5576 | 5732 | _ = t; |
| 5577 | 5733 | const w = windows; |
| 5578 | 5734 | |
| 5579 | | try Thread.checkCancel(); |
| 5580 | | |
| 5581 | 5735 | var sub_path_w_buf = try windows.sliceToPrefixedFileW(dir.handle, sub_path); |
| 5582 | 5736 | |
| 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 | }; |
| 5584 | 5750 | |
| 5585 | 5751 | const len = std.unicode.calcWtf8Len(result_w); |
| 5586 | 5752 | if (len > buffer.len) return error.NameTooLong; |
| ... | ... | @@ -5997,17 +6163,25 @@ fn fileSyncWindows(userdata: ?*anyopaque, file: File) File.SyncError!void { |
| 5997 | 6163 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 5998 | 6164 | _ = t; |
| 5999 | 6165 | |
| 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 | } |
| 6011 | 6185 | } |
| 6012 | 6186 | } |
| 6013 | 6187 | |
| ... | ... | @@ -6074,9 +6248,22 @@ fn fileIsTty(userdata: ?*anyopaque, file: File) Io.Cancelable!bool { |
| 6074 | 6248 | fn isTty(file: File) Io.Cancelable!bool { |
| 6075 | 6249 | if (is_windows) { |
| 6076 | 6250 | if (try isCygwinPty(file)) return true; |
| 6077 | | try Thread.checkCancel(); |
| 6078 | 6251 | var out: windows.DWORD = undefined; |
| 6079 | | return windows.kernel32.GetConsoleMode(file.handle, &out) != 0; |
| 6252 | const syscall: Syscall = try .start(); |
| 6253 | while (windows.kernel32.GetConsoleMode(file.handle, &out) == 0) { |
| 6254 | switch (windows.GetLastError()) { |
| 6255 | .OPERATION_ABORTED => { |
| 6256 | try syscall.checkCancel(); |
| 6257 | continue; |
| 6258 | }, |
| 6259 | else => { |
| 6260 | syscall.finish(); |
| 6261 | return false; |
| 6262 | }, |
| 6263 | } |
| 6264 | } |
| 6265 | syscall.finish(); |
| 6266 | return true; |
| 6080 | 6267 | } |
| 6081 | 6268 | |
| 6082 | 6269 | if (builtin.link_libc) { |
| ... | ... | @@ -6146,35 +6333,65 @@ fn fileEnableAnsiEscapeCodes(userdata: ?*anyopaque, file: File) File.EnableAnsiE |
| 6146 | 6333 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 6147 | 6334 | _ = t; |
| 6148 | 6335 | |
| 6149 | | if (is_windows) { |
| 6150 | | try Thread.checkCancel(); |
| 6336 | if (!is_windows) { |
| 6337 | if (try supportsAnsiEscapeCodes(file)) return; |
| 6338 | return error.NotTerminalDevice; |
| 6339 | } |
| 6151 | 6340 | |
| 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; |
| 6156 | 6343 | |
| 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 | } |
| 6172 | 6358 | } |
| 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(); |
| 6176 | 6394 | } |
| 6177 | | return error.NotTerminalDevice; |
| 6178 | 6395 | } |
| 6179 | 6396 | |
| 6180 | 6397 | fn fileSupportsAnsiEscapeCodes(userdata: ?*anyopaque, file: File) Io.Cancelable!bool { |
| ... | ... | @@ -6185,11 +6402,27 @@ fn fileSupportsAnsiEscapeCodes(userdata: ?*anyopaque, file: File) Io.Cancelable! |
| 6185 | 6402 | |
| 6186 | 6403 | fn supportsAnsiEscapeCodes(file: File) Io.Cancelable!bool { |
| 6187 | 6404 | if (is_windows) { |
| 6188 | | try Thread.checkCancel(); |
| 6189 | 6405 | 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 | } |
| 6192 | 6424 | } |
| 6425 | |
| 6193 | 6426 | return isCygwinPty(file); |
| 6194 | 6427 | } |
| 6195 | 6428 | |
| ... | ... | @@ -6220,20 +6453,26 @@ fn isCygwinPty(file: File) Io.Cancelable!bool { |
| 6220 | 6453 | // This allows us to avoid the more costly NtQueryInformationFile call |
| 6221 | 6454 | // for handles that aren't named pipes. |
| 6222 | 6455 | { |
| 6223 | | try Thread.checkCancel(); |
| 6224 | 6456 | var io_status: windows.IO_STATUS_BLOCK = undefined; |
| 6225 | 6457 | var device_info: windows.FILE.FS_DEVICE_INFORMATION = undefined; |
| 6226 | | const rc = windows.ntdll.NtQueryVolumeInformationFile( |
| 6458 | const syscall: Syscall = try .start(); |
| 6459 | while (true) switch (windows.ntdll.NtQueryVolumeInformationFile( |
| 6227 | 6460 | handle, |
| 6228 | 6461 | &io_status, |
| 6229 | 6462 | &device_info, |
| 6230 | 6463 | @sizeOf(windows.FILE.FS_DEVICE_INFORMATION), |
| 6231 | 6464 | .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 | }; |
| 6237 | 6476 | if (device_info.DeviceType.FileDevice != .NAMED_PIPE) return false; |
| 6238 | 6477 | } |
| 6239 | 6478 | |
| ... | ... | @@ -6248,19 +6487,25 @@ fn isCygwinPty(file: File) Io.Cancelable!bool { |
| 6248 | 6487 | var name_info_bytes align(@alignOf(windows.FILE.NAME_INFORMATION)) = [_]u8{0} ** (name_bytes_offset + num_name_bytes); |
| 6249 | 6488 | |
| 6250 | 6489 | 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( |
| 6253 | 6492 | handle, |
| 6254 | 6493 | &io_status_block, |
| 6255 | 6494 | &name_info_bytes, |
| 6256 | 6495 | @intCast(name_info_bytes.len), |
| 6257 | 6496 | .Name, |
| 6258 | | ); |
| 6259 | | switch (rc) { |
| 6260 | | .SUCCESS => {}, |
| 6497 | )) { |
| 6498 | .SUCCESS => break syscall.finish(), |
| 6499 | .CANCELLED => { |
| 6500 | try syscall.checkCancel(); |
| 6501 | continue; |
| 6502 | }, |
| 6261 | 6503 | .INVALID_PARAMETER => unreachable, |
| 6262 | | else => return false, |
| 6263 | | } |
| 6504 | else => { |
| 6505 | syscall.finish(); |
| 6506 | return false; |
| 6507 | }, |
| 6508 | }; |
| 6264 | 6509 | |
| 6265 | 6510 | const name_info: *const windows.FILE_NAME_INFO = @ptrCast(&name_info_bytes); |
| 6266 | 6511 | const name_bytes = name_info_bytes[name_bytes_offset .. name_bytes_offset + name_info.FileNameLength]; |
| ... | ... | @@ -6279,28 +6524,30 @@ fn fileSetLength(userdata: ?*anyopaque, file: File, length: u64) File.SetLengthE |
| 6279 | 6524 | if (signed_len < 0) return error.FileTooBig; // Avoid ambiguous EINVAL errors. |
| 6280 | 6525 | |
| 6281 | 6526 | if (is_windows) { |
| 6282 | | try Thread.checkCancel(); |
| 6283 | | |
| 6284 | 6527 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 6285 | 6528 | const eof_info: windows.FILE.END_OF_FILE_INFORMATION = .{ |
| 6286 | 6529 | .EndOfFile = signed_len, |
| 6287 | 6530 | }; |
| 6288 | 6531 | |
| 6289 | | const status = windows.ntdll.NtSetInformationFile( |
| 6532 | const syscall: Syscall = try .start(); |
| 6533 | while (true) switch (windows.ntdll.NtSetInformationFile( |
| 6290 | 6534 | file.handle, |
| 6291 | 6535 | &io_status_block, |
| 6292 | 6536 | &eof_info, |
| 6293 | 6537 | @sizeOf(windows.FILE.END_OF_FILE_INFORMATION), |
| 6294 | 6538 | .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 | }; |
| 6304 | 6551 | } |
| 6305 | 6552 | |
| 6306 | 6553 | if (native_os == .wasi and !builtin.link_libc) { |
| ... | ... | @@ -6368,7 +6615,6 @@ fn fileSetPermissions(userdata: ?*anyopaque, file: File, permissions: File.Permi |
| 6368 | 6615 | _ = t; |
| 6369 | 6616 | switch (native_os) { |
| 6370 | 6617 | .windows => { |
| 6371 | | try Thread.checkCancel(); |
| 6372 | 6618 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 6373 | 6619 | const info: windows.FILE.BASIC_INFORMATION = .{ |
| 6374 | 6620 | .CreationTime = 0, |
| ... | ... | @@ -6377,19 +6623,23 @@ fn fileSetPermissions(userdata: ?*anyopaque, file: File, permissions: File.Permi |
| 6377 | 6623 | .ChangeTime = 0, |
| 6378 | 6624 | .FileAttributes = permissions.toAttributes(), |
| 6379 | 6625 | }; |
| 6380 | | const status = windows.ntdll.NtSetInformationFile( |
| 6626 | const syscall: Syscall = try .start(); |
| 6627 | while (true) switch (windows.ntdll.NtSetInformationFile( |
| 6381 | 6628 | file.handle, |
| 6382 | 6629 | &io_status_block, |
| 6383 | 6630 | &info, |
| 6384 | 6631 | @sizeOf(windows.FILE.BASIC_INFORMATION), |
| 6385 | 6632 | .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 | }; |
| 6393 | 6643 | }, |
| 6394 | 6644 | .wasi => return error.Unexpected, // Unsupported OS. |
| 6395 | 6645 | else => return setPermissionsPosix(file.handle, permissions.toMode()), |
| ... | ... | @@ -6484,8 +6734,6 @@ fn fileSetTimestamps( |
| 6484 | 6734 | _ = t; |
| 6485 | 6735 | |
| 6486 | 6736 | if (is_windows) { |
| 6487 | | try Thread.checkCancel(); |
| 6488 | | |
| 6489 | 6737 | var access_time_buffer: windows.FILETIME = undefined; |
| 6490 | 6738 | var modify_time_buffer: windows.FILETIME = undefined; |
| 6491 | 6739 | var system_time_buffer: windows.LARGE_INTEGER = undefined; |
| ... | ... | @@ -6513,13 +6761,22 @@ fn fileSetTimestamps( |
| 6513 | 6761 | }; |
| 6514 | 6762 | |
| 6515 | 6763 | // 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(), |
| 6520 | 6778 | } |
| 6521 | 6779 | } |
| 6522 | | return; |
| 6523 | 6780 | } |
| 6524 | 6781 | |
| 6525 | 6782 | if (native_os == .wasi and !builtin.link_libc) { |
| ... | ... | @@ -6601,27 +6858,26 @@ fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!v |
| 6601 | 6858 | .none => { |
| 6602 | 6859 | // To match the non-Windows behavior, unlock |
| 6603 | 6860 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 6604 | | const status = windows.ntdll.NtUnlockFile( |
| 6861 | while (true) switch (windows.ntdll.NtUnlockFile( |
| 6605 | 6862 | file.handle, |
| 6606 | 6863 | &io_status_block, |
| 6607 | 6864 | &windows_lock_range_off, |
| 6608 | 6865 | &windows_lock_range_len, |
| 6609 | 6866 | 0, |
| 6610 | | ); |
| 6611 | | switch (status) { |
| 6612 | | .SUCCESS => {}, |
| 6613 | | .RANGE_NOT_LOCKED => {}, |
| 6867 | )) { |
| 6868 | .SUCCESS => return, |
| 6869 | .CANCELLED => continue, |
| 6870 | .RANGE_NOT_LOCKED => return, |
| 6614 | 6871 | .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 | }; |
| 6618 | 6874 | }, |
| 6619 | 6875 | .shared => false, |
| 6620 | 6876 | .exclusive => true, |
| 6621 | 6877 | }; |
| 6622 | | try Thread.checkCancel(); |
| 6623 | 6878 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 6624 | | const status = windows.ntdll.NtLockFile( |
| 6879 | const syscall: Syscall = try .start(); |
| 6880 | while (true) switch (windows.ntdll.NtLockFile( |
| 6625 | 6881 | file.handle, |
| 6626 | 6882 | null, |
| 6627 | 6883 | null, |
| ... | ... | @@ -6632,14 +6888,17 @@ fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!v |
| 6632 | 6888 | null, |
| 6633 | 6889 | windows.FALSE, |
| 6634 | 6890 | @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 | }; |
| 6643 | 6902 | } |
| 6644 | 6903 | |
| 6645 | 6904 | const operation: i32 = switch (lock) { |
| ... | ... | @@ -6680,26 +6939,26 @@ fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockErro |
| 6680 | 6939 | .none => { |
| 6681 | 6940 | // To match the non-Windows behavior, unlock |
| 6682 | 6941 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 6683 | | const status = windows.ntdll.NtUnlockFile( |
| 6942 | while (true) switch (windows.ntdll.NtUnlockFile( |
| 6684 | 6943 | file.handle, |
| 6685 | 6944 | &io_status_block, |
| 6686 | 6945 | &windows_lock_range_off, |
| 6687 | 6946 | &windows_lock_range_len, |
| 6688 | 6947 | 0, |
| 6689 | | ); |
| 6690 | | switch (status) { |
| 6948 | )) { |
| 6691 | 6949 | .SUCCESS => return true, |
| 6950 | .CANCELLED => continue, |
| 6692 | 6951 | .RANGE_NOT_LOCKED => return false, |
| 6693 | 6952 | .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 | }; |
| 6696 | 6955 | }, |
| 6697 | 6956 | .shared => false, |
| 6698 | 6957 | .exclusive => true, |
| 6699 | 6958 | }; |
| 6700 | | try Thread.checkCancel(); |
| 6701 | 6959 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 6702 | | const status = windows.ntdll.NtLockFile( |
| 6960 | const syscall: Syscall = try .start(); |
| 6961 | while (true) switch (windows.ntdll.NtLockFile( |
| 6703 | 6962 | file.handle, |
| 6704 | 6963 | null, |
| 6705 | 6964 | null, |
| ... | ... | @@ -6710,14 +6969,23 @@ fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockErro |
| 6710 | 6969 | null, |
| 6711 | 6970 | windows.TRUE, |
| 6712 | 6971 | @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 | }; |
| 6721 | 6989 | } |
| 6722 | 6990 | |
| 6723 | 6991 | const operation: i32 = switch (lock) { |
| ... | ... | @@ -6761,20 +7029,19 @@ fn fileUnlock(userdata: ?*anyopaque, file: File) void { |
| 6761 | 7029 | |
| 6762 | 7030 | if (is_windows) { |
| 6763 | 7031 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 6764 | | const status = windows.ntdll.NtUnlockFile( |
| 7032 | while (true) switch (windows.ntdll.NtUnlockFile( |
| 6765 | 7033 | file.handle, |
| 6766 | 7034 | &io_status_block, |
| 6767 | 7035 | &windows_lock_range_off, |
| 6768 | 7036 | &windows_lock_range_len, |
| 6769 | 7037 | 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. |
| 6776 | 7044 | }; |
| 6777 | | return; |
| 6778 | 7045 | } |
| 6779 | 7046 | |
| 6780 | 7047 | while (true) { |
| ... | ... | @@ -6797,14 +7064,14 @@ fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError! |
| 6797 | 7064 | _ = t; |
| 6798 | 7065 | |
| 6799 | 7066 | if (is_windows) { |
| 6800 | | try Thread.checkCancel(); |
| 6801 | 7067 | // On Windows it works like a semaphore + exclusivity flag. To |
| 6802 | 7068 | // implement this function, we first obtain another lock in shared |
| 6803 | 7069 | // mode. This changes the exclusivity flag, but increments the |
| 6804 | 7070 | // semaphore to 2. So we follow up with an NtUnlockFile which |
| 6805 | 7071 | // decrements the semaphore but does not modify the exclusivity flag. |
| 6806 | 7072 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 6807 | | switch (windows.ntdll.NtLockFile( |
| 7073 | const syscall: Syscall = try .start(); |
| 7074 | while (true) switch (windows.ntdll.NtLockFile( |
| 6808 | 7075 | file.handle, |
| 6809 | 7076 | null, |
| 6810 | 7077 | null, |
| ... | ... | @@ -6816,26 +7083,29 @@ fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError! |
| 6816 | 7083 | windows.TRUE, |
| 6817 | 7084 | windows.FALSE, |
| 6818 | 7085 | )) { |
| 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( |
| 6826 | 7097 | file.handle, |
| 6827 | 7098 | &io_status_block, |
| 6828 | 7099 | &windows_lock_range_off, |
| 6829 | 7100 | &windows_lock_range_len, |
| 6830 | 7101 | 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. |
| 6837 | 7108 | }; |
| 6838 | | return; |
| 6839 | 7109 | } |
| 6840 | 7110 | |
| 6841 | 7111 | const operation = posix.LOCK.SH | posix.LOCK.NB; |
| ... | ... | @@ -7158,21 +7428,34 @@ fn fileReadStreamingWindows(userdata: ?*anyopaque, file: File, data: []const []u |
| 7158 | 7428 | const buffer = data[index]; |
| 7159 | 7429 | const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len); |
| 7160 | 7430 | |
| 7431 | const syscall: Syscall = try .start(); |
| 7161 | 7432 | while (true) { |
| 7162 | | try Thread.checkCancel(); |
| 7163 | 7433 | var n: DWORD = undefined; |
| 7164 | | if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) != 0) |
| 7434 | if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) != 0) { |
| 7435 | syscall.finish(); |
| 7165 | 7436 | return n; |
| 7437 | } |
| 7166 | 7438 | 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 | }, |
| 7176 | 7459 | } |
| 7177 | 7460 | } |
| 7178 | 7461 | } |
| ... | ... | @@ -7302,21 +7585,34 @@ fn fileReadPositionalWindows(userdata: ?*anyopaque, file: File, data: []const [] |
| 7302 | 7585 | .hEvent = null, |
| 7303 | 7586 | }; |
| 7304 | 7587 | |
| 7588 | const syscall: Syscall = try .start(); |
| 7305 | 7589 | while (true) { |
| 7306 | | try Thread.checkCancel(); |
| 7307 | 7590 | var n: DWORD = undefined; |
| 7308 | | if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, &overlapped) != 0) |
| 7591 | if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, &overlapped) != 0) { |
| 7592 | syscall.finish(); |
| 7309 | 7593 | return n; |
| 7594 | } |
| 7310 | 7595 | 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 | }, |
| 7320 | 7616 | } |
| 7321 | 7617 | } |
| 7322 | 7618 | } |
| ... | ... | @@ -7355,8 +7651,26 @@ fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!voi |
| 7355 | 7651 | } |
| 7356 | 7652 | |
| 7357 | 7653 | 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 | } |
| 7360 | 7674 | } |
| 7361 | 7675 | |
| 7362 | 7676 | if (native_os == .wasi and !builtin.link_libc) { |
| ... | ... | @@ -7422,8 +7736,31 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!voi |
| 7422 | 7736 | const fd = file.handle; |
| 7423 | 7737 | |
| 7424 | 7738 | 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 | } |
| 7427 | 7764 | } |
| 7428 | 7765 | |
| 7429 | 7766 | if (native_os == .wasi and !builtin.link_libc) { |
| ... | ... | @@ -7527,7 +7864,7 @@ fn processExecutableOpen(userdata: ?*anyopaque, flags: File.OpenFlags) std.proce |
| 7527 | 7864 | const image_path_unicode_string = &windows.peb().ProcessParameters.ImagePathName; |
| 7528 | 7865 | const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0]; |
| 7529 | 7866 | const prefixed_path_w = try windows.wToPrefixedFileW(null, image_path_name); |
| 7530 | | return dirOpenFileWtf16(t, null, prefixed_path_w.span(), flags); |
| 7867 | return dirOpenFileWtf16(null, prefixed_path_w.span(), flags); |
| 7531 | 7868 | }, |
| 7532 | 7869 | .driverkit, |
| 7533 | 7870 | .ios, |
| ... | ... | @@ -7736,7 +8073,6 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex |
| 7736 | 8073 | return error.FileNotFound; |
| 7737 | 8074 | }, |
| 7738 | 8075 | .windows => { |
| 7739 | | try Thread.checkCancel(); |
| 7740 | 8076 | const w = windows; |
| 7741 | 8077 | const image_path_unicode_string = &w.peb().ProcessParameters.ImagePathName; |
| 7742 | 8078 | const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0]; |
| ... | ... | @@ -7746,24 +8082,34 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex |
| 7746 | 8082 | // that the symlink points to, though, so we need to get the realpath. |
| 7747 | 8083 | var path_name_w_buf = try w.wToPrefixedFileW(null, image_path_name); |
| 7748 | 8084 | |
| 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 | } |
| 7763 | 8108 | }; |
| 7764 | 8109 | defer w.CloseHandle(h_file); |
| 7765 | 8110 | |
| 7766 | 8111 | // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks |
| 8112 | try Thread.checkCancel(); |
| 7767 | 8113 | const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, &path_name_w_buf.data); |
| 7768 | 8114 | |
| 7769 | 8115 | const len = std.unicode.calcWtf8Len(wide_slice); |
| ... | ... | @@ -7916,8 +8262,6 @@ fn writeFilePositionalWindows( |
| 7916 | 8262 | bytes: []const u8, |
| 7917 | 8263 | offset: u64, |
| 7918 | 8264 | ) File.WritePositionalError!usize { |
| 7919 | | try Thread.checkCancel(); |
| 7920 | | |
| 7921 | 8265 | var bytes_written: windows.DWORD = undefined; |
| 7922 | 8266 | var overlapped: windows.OVERLAPPED = .{ |
| 7923 | 8267 | .Internal = 0, |
| ... | ... | @@ -7931,21 +8275,31 @@ fn writeFilePositionalWindows( |
| 7931 | 8275 | .hEvent = null, |
| 7932 | 8276 | }; |
| 7933 | 8277 | const adjusted_len = std.math.lossyCast(u32, bytes.len); |
| 7934 | | if (windows.kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, &overlapped) == 0) { |
| 8278 | const syscall: Syscall = try .start(); |
| 8279 | while (true) { |
| 8280 | if (windows.kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, &overlapped) != 0) { |
| 8281 | syscall.finish(); |
| 8282 | return bytes_written; |
| 8283 | } |
| 7935 | 8284 | 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 | }, |
| 7946 | 8301 | } |
| 7947 | 8302 | } |
| 7948 | | return bytes_written; |
| 7949 | 8303 | } |
| 7950 | 8304 | |
| 7951 | 8305 | fn fileWriteStreaming( |
| ... | ... | @@ -8078,25 +8432,33 @@ fn writeFileStreamingWindows( |
| 8078 | 8432 | handle: windows.HANDLE, |
| 8079 | 8433 | bytes: []const u8, |
| 8080 | 8434 | ) File.Writer.Error!usize { |
| 8081 | | try Thread.checkCancel(); |
| 8082 | | |
| 8083 | 8435 | var bytes_written: windows.DWORD = undefined; |
| 8084 | 8436 | const adjusted_len = std.math.lossyCast(u32, bytes.len); |
| 8085 | | if (windows.kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, null) == 0) { |
| 8437 | const syscall: Syscall = try .start(); |
| 8438 | while (true) { |
| 8439 | if (windows.kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, null) != 0) { |
| 8440 | syscall.finish(); |
| 8441 | return bytes_written; |
| 8442 | } |
| 8086 | 8443 | 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 | }, |
| 8097 | 8460 | } |
| 8098 | 8461 | } |
| 8099 | | return bytes_written; |
| 8100 | 8462 | } |
| 8101 | 8463 | |
| 8102 | 8464 | fn fileWriteFileStreaming( |
| ... | ... | @@ -8716,9 +9078,7 @@ fn fileWriteFilePositional( |
| 8716 | 9078 | return error.Unimplemented; |
| 8717 | 9079 | } |
| 8718 | 9080 | |
| 8719 | | fn nowPosix(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp { |
| 8720 | | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 8721 | | _ = t; |
| 9081 | fn nowPosix(clock: Io.Clock) Io.Clock.Error!Io.Timestamp { |
| 8722 | 9082 | const clock_id: posix.clockid_t = clockToPosix(clock); |
| 8723 | 9083 | var tp: posix.timespec = undefined; |
| 8724 | 9084 | switch (posix.errno(posix.system.clock_gettime(clock_id, &tp))) { |
| ... | ... | @@ -8728,15 +9088,17 @@ fn nowPosix(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp |
| 8728 | 9088 | } |
| 8729 | 9089 | } |
| 8730 | 9090 | |
| 8731 | | const now = switch (native_os) { |
| 8732 | | .windows => nowWindows, |
| 8733 | | .wasi => nowWasi, |
| 8734 | | else => nowPosix, |
| 8735 | | }; |
| 8736 | | |
| 8737 | | fn nowWindows(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp { |
| 9091 | fn now(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp { |
| 8738 | 9092 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 8739 | 9093 | _ = t; |
| 9094 | return switch (native_os) { |
| 9095 | .windows => nowWindows(clock), |
| 9096 | .wasi => nowWasi(clock), |
| 9097 | else => nowPosix(clock), |
| 9098 | }; |
| 9099 | } |
| 9100 | |
| 9101 | fn nowWindows(clock: Io.Clock) Io.Clock.Error!Io.Timestamp { |
| 8740 | 9102 | switch (clock) { |
| 8741 | 9103 | .real => { |
| 8742 | 9104 | // RtlGetSystemTimePrecise() has a granularity of 100 nanoseconds |
| ... | ... | @@ -8769,25 +9131,24 @@ fn nowWindows(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestam |
| 8769 | 9131 | } |
| 8770 | 9132 | } |
| 8771 | 9133 | |
| 8772 | | fn nowWasi(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp { |
| 8773 | | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 8774 | | _ = t; |
| 9134 | fn nowWasi(clock: Io.Clock) Io.Clock.Error!Io.Timestamp { |
| 8775 | 9135 | var ns: std.os.wasi.timestamp_t = undefined; |
| 8776 | 9136 | const err = std.os.wasi.clock_time_get(clockToWasi(clock), 1, &ns); |
| 8777 | 9137 | if (err != .SUCCESS) return error.Unexpected; |
| 8778 | 9138 | return .fromNanoseconds(ns); |
| 8779 | 9139 | } |
| 8780 | 9140 | |
| 8781 | | const sleep = switch (native_os) { |
| 8782 | | .windows => sleepWindows, |
| 8783 | | .wasi => sleepWasi, |
| 8784 | | .linux => sleepLinux, |
| 8785 | | else => sleepPosix, |
| 8786 | | }; |
| 8787 | | |
| 8788 | | fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { |
| 9141 | fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { |
| 8789 | 9142 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 8790 | | _ = t; |
| 9143 | if (use_parking_sleep) return parking_sleep.sleep(timeout); |
| 9144 | switch (native_os) { |
| 9145 | .wasi => return sleepWasi(t, timeout), |
| 9146 | .linux => return sleepLinux(timeout), |
| 9147 | else => return sleepPosix(t, timeout), |
| 9148 | } |
| 9149 | } |
| 9150 | |
| 9151 | fn sleepLinux(timeout: Io.Timeout) Io.SleepError!void { |
| 8791 | 9152 | const clock_id: posix.clockid_t = clockToPosix(switch (timeout) { |
| 8792 | 9153 | .none => .awake, |
| 8793 | 9154 | .duration => |d| d.clock, |
| ... | ... | @@ -8824,21 +9185,7 @@ fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { |
| 8824 | 9185 | } |
| 8825 | 9186 | } |
| 8826 | 9187 | |
| 8827 | | fn 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 | | |
| 8840 | | fn sleepWasi(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { |
| 8841 | | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 9188 | fn sleepWasi(t: *Threaded, timeout: Io.Timeout) Io.SleepError!void { |
| 8842 | 9189 | const t_io = ioBasic(t); |
| 8843 | 9190 | const w = std.os.wasi; |
| 8844 | 9191 | |
| ... | ... | @@ -8867,8 +9214,7 @@ fn sleepWasi(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { |
| 8867 | 9214 | syscall.finish(); |
| 8868 | 9215 | } |
| 8869 | 9216 | |
| 8870 | | fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { |
| 8871 | | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 9217 | fn sleepPosix(t: *Threaded, timeout: Io.Timeout) Io.SleepError!void { |
| 8872 | 9218 | const t_io = ioBasic(t); |
| 8873 | 9219 | const sec_type = @typeInfo(posix.timespec).@"struct".fields[0].type; |
| 8874 | 9220 | const nsec_type = @typeInfo(posix.timespec).@"struct".fields[1].type; |
| ... | ... | @@ -9037,93 +9383,90 @@ fn netListenIpWindows( |
| 9037 | 9383 | var storage: WsaAddress = undefined; |
| 9038 | 9384 | var addr_len = addressToWsa(&address, &storage); |
| 9039 | 9385 | |
| 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; |
| 9073 | 9392 | } |
| 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 => { |
| 9080 | 9395 | 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 | }, |
| 9109 | 9417 | } |
| 9110 | 9418 | } |
| 9111 | 9419 | |
| 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 | | |
| 9122 | | fn 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 | |
| 9465 | fn netListenIpUnavailable( |
| 9466 | userdata: ?*anyopaque, |
| 9467 | address: IpAddress, |
| 9468 | options: IpAddress.ListenOptions, |
| 9469 | ) IpAddress.ListenError!net.Server { |
| 9127 | 9470 | _ = userdata; |
| 9128 | 9471 | _ = address; |
| 9129 | 9472 | _ = options; |
| ... | ... | @@ -9193,24 +9536,24 @@ fn netListenUnixWindows( |
| 9193 | 9536 | var storage: WsaAddress = undefined; |
| 9194 | 9537 | const addr_len = addressUnixToWsa(address, &storage); |
| 9195 | 9538 | |
| 9196 | | const syscall: Syscall = try .start(); |
| 9539 | var syscall: Syscall = try .start(); |
| 9197 | 9540 | while (true) { |
| 9198 | 9541 | const rc = ws2_32.bind(socket_handle, &storage.any, addr_len); |
| 9199 | 9542 | if (rc != ws2_32.SOCKET_ERROR) break; |
| 9200 | 9543 | switch (ws2_32.WSAGetLastError()) { |
| 9201 | | .EINTR => { |
| 9202 | | try syscall.checkCancel(); |
| 9203 | | continue; |
| 9204 | | }, |
| 9205 | 9544 | .NOTINITIALISED => { |
| 9545 | syscall.finish(); |
| 9206 | 9546 | try initializeWsa(t); |
| 9547 | syscall = try .start(); |
| 9548 | continue; |
| 9549 | }, |
| 9550 | .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { |
| 9207 | 9551 | try syscall.checkCancel(); |
| 9208 | 9552 | continue; |
| 9209 | 9553 | }, |
| 9210 | 9554 | else => |e| { |
| 9211 | 9555 | syscall.finish(); |
| 9212 | 9556 | switch (e) { |
| 9213 | | .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, |
| 9214 | 9557 | .EADDRINUSE => return error.AddressInUse, |
| 9215 | 9558 | .EADDRNOTAVAIL => return error.AddressUnavailable, |
| 9216 | 9559 | .ENOTSOCK => |err| return wsaErrorBug(err), |
| ... | ... | @@ -9232,15 +9575,16 @@ fn netListenUnixWindows( |
| 9232 | 9575 | return socket_handle; |
| 9233 | 9576 | } |
| 9234 | 9577 | switch (ws2_32.WSAGetLastError()) { |
| 9235 | | .EINTR => continue, |
| 9578 | .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => continue, |
| 9236 | 9579 | .NOTINITIALISED => { |
| 9580 | syscall.finish(); |
| 9237 | 9581 | try initializeWsa(t); |
| 9582 | syscall = try .start(); |
| 9238 | 9583 | continue; |
| 9239 | 9584 | }, |
| 9240 | 9585 | else => |e| { |
| 9241 | 9586 | syscall.finish(); |
| 9242 | 9587 | switch (e) { |
| 9243 | | .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, |
| 9244 | 9588 | .ENETDOWN => return error.NetworkDown, |
| 9245 | 9589 | .EADDRINUSE => return error.AddressInUse, |
| 9246 | 9590 | .EISCONN => |err| return wsaErrorBug(err), |
| ... | ... | @@ -9469,7 +9813,7 @@ fn wsaGetSockName( |
| 9469 | 9813 | addr: *ws2_32.sockaddr, |
| 9470 | 9814 | addr_len: *i32, |
| 9471 | 9815 | ) !void { |
| 9472 | | const syscall: Syscall = try .start(); |
| 9816 | var syscall: Syscall = try .start(); |
| 9473 | 9817 | while (true) { |
| 9474 | 9818 | const rc = ws2_32.getsockname(handle, addr, addr_len); |
| 9475 | 9819 | if (rc != ws2_32.SOCKET_ERROR) { |
| ... | ... | @@ -9477,19 +9821,19 @@ fn wsaGetSockName( |
| 9477 | 9821 | return; |
| 9478 | 9822 | } |
| 9479 | 9823 | switch (ws2_32.WSAGetLastError()) { |
| 9480 | | .EINTR => { |
| 9824 | .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { |
| 9481 | 9825 | try syscall.checkCancel(); |
| 9482 | 9826 | continue; |
| 9483 | 9827 | }, |
| 9484 | 9828 | .NOTINITIALISED => { |
| 9829 | syscall.finish(); |
| 9485 | 9830 | try initializeWsa(t); |
| 9486 | | try syscall.checkCancel(); |
| 9831 | syscall = try .start(); |
| 9487 | 9832 | continue; |
| 9488 | 9833 | }, |
| 9489 | 9834 | else => |e| { |
| 9490 | 9835 | syscall.finish(); |
| 9491 | 9836 | switch (e) { |
| 9492 | | .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, |
| 9493 | 9837 | .ENETDOWN => return error.NetworkDown, |
| 9494 | 9838 | .EFAULT => |err| return wsaErrorBug(err), |
| 9495 | 9839 | .ENOTSOCK => |err| return wsaErrorBug(err), |
| ... | ... | @@ -9530,21 +9874,30 @@ fn setSocketOption(fd: posix.fd_t, level: i32, opt_name: u32, option: u32) !void |
| 9530 | 9874 | |
| 9531 | 9875 | fn setSocketOptionWsa(t: *Threaded, socket: Io.net.Socket.Handle, level: i32, opt_name: u32, option: u32) !void { |
| 9532 | 9876 | const o: []const u8 = @ptrCast(&option); |
| 9877 | var syscall: Syscall = try .start(); |
| 9533 | 9878 | const rc = ws2_32.setsockopt(socket, level, @bitCast(opt_name), o.ptr, @intCast(o.len)); |
| 9534 | 9879 | while (true) { |
| 9535 | | if (rc != ws2_32.SOCKET_ERROR) return; |
| 9880 | if (rc != ws2_32.SOCKET_ERROR) return syscall.finish(); |
| 9536 | 9881 | 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 | }, |
| 9539 | 9886 | .NOTINITIALISED => { |
| 9887 | syscall.finish(); |
| 9540 | 9888 | try initializeWsa(t); |
| 9889 | syscall = try .start(); |
| 9541 | 9890 | continue; |
| 9542 | 9891 | }, |
| 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 | }, |
| 9548 | 9901 | } |
| 9549 | 9902 | } |
| 9550 | 9903 | } |
| ... | ... | @@ -9592,7 +9945,7 @@ fn netConnectIpWindows( |
| 9592 | 9945 | var storage: WsaAddress = undefined; |
| 9593 | 9946 | var addr_len = addressToWsa(address, &storage); |
| 9594 | 9947 | |
| 9595 | | const syscall: Syscall = try .start(); |
| 9948 | var syscall: Syscall = try .start(); |
| 9596 | 9949 | while (true) { |
| 9597 | 9950 | const rc = ws2_32.connect(socket_handle, &storage.any, addr_len); |
| 9598 | 9951 | if (rc != ws2_32.SOCKET_ERROR) { |
| ... | ... | @@ -9600,19 +9953,19 @@ fn netConnectIpWindows( |
| 9600 | 9953 | break; |
| 9601 | 9954 | } |
| 9602 | 9955 | switch (ws2_32.WSAGetLastError()) { |
| 9603 | | .EINTR => { |
| 9956 | .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { |
| 9604 | 9957 | try syscall.checkCancel(); |
| 9605 | 9958 | continue; |
| 9606 | 9959 | }, |
| 9607 | 9960 | .NOTINITIALISED => { |
| 9961 | syscall.finish(); |
| 9608 | 9962 | try initializeWsa(t); |
| 9609 | | try syscall.checkCancel(); |
| 9963 | syscall = try .start(); |
| 9610 | 9964 | continue; |
| 9611 | 9965 | }, |
| 9612 | 9966 | else => |e| { |
| 9613 | 9967 | syscall.finish(); |
| 9614 | 9968 | switch (e) { |
| 9615 | | .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, |
| 9616 | 9969 | .EADDRNOTAVAIL => return error.AddressUnavailable, |
| 9617 | 9970 | .ECONNREFUSED => return error.ConnectionRefused, |
| 9618 | 9971 | .ECONNRESET => return error.ConnectionResetByPeer, |
| ... | ... | @@ -9682,27 +10035,36 @@ fn netConnectUnixWindows( |
| 9682 | 10035 | var storage: WsaAddress = undefined; |
| 9683 | 10036 | const addr_len = addressUnixToWsa(address, &storage); |
| 9684 | 10037 | |
| 10038 | var syscall: Syscall = try .start(); |
| 9685 | 10039 | while (true) { |
| 9686 | 10040 | const rc = ws2_32.connect(socket_handle, &storage.any, addr_len); |
| 9687 | 10041 | if (rc != ws2_32.SOCKET_ERROR) break; |
| 9688 | 10042 | 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 | }, |
| 9691 | 10047 | .NOTINITIALISED => { |
| 10048 | syscall.finish(); |
| 9692 | 10049 | try initializeWsa(t); |
| 10050 | syscall = try .start(); |
| 9693 | 10051 | continue; |
| 9694 | 10052 | }, |
| 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 | }, |
| 9706 | 10068 | } |
| 9707 | 10069 | } |
| 9708 | 10070 | |
| ... | ... | @@ -9756,7 +10118,7 @@ fn netBindIpWindows( |
| 9756 | 10118 | var storage: WsaAddress = undefined; |
| 9757 | 10119 | var addr_len = addressToWsa(address, &storage); |
| 9758 | 10120 | |
| 9759 | | const syscall: Syscall = try .start(); |
| 10121 | var syscall: Syscall = try .start(); |
| 9760 | 10122 | while (true) { |
| 9761 | 10123 | const rc = ws2_32.bind(socket_handle, &storage.any, addr_len); |
| 9762 | 10124 | if (rc != ws2_32.SOCKET_ERROR) { |
| ... | ... | @@ -9764,19 +10126,19 @@ fn netBindIpWindows( |
| 9764 | 10126 | break; |
| 9765 | 10127 | } |
| 9766 | 10128 | switch (ws2_32.WSAGetLastError()) { |
| 9767 | | .EINTR => { |
| 10129 | .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { |
| 9768 | 10130 | try syscall.checkCancel(); |
| 9769 | 10131 | continue; |
| 9770 | 10132 | }, |
| 9771 | 10133 | .NOTINITIALISED => { |
| 10134 | syscall.finish(); |
| 9772 | 10135 | try initializeWsa(t); |
| 9773 | | try syscall.checkCancel(); |
| 10136 | syscall = try .start(); |
| 9774 | 10137 | continue; |
| 9775 | 10138 | }, |
| 9776 | 10139 | else => |e| { |
| 9777 | 10140 | syscall.finish(); |
| 9778 | 10141 | switch (e) { |
| 9779 | | .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, |
| 9780 | 10142 | .EADDRINUSE => return error.AddressInUse, |
| 9781 | 10143 | .EADDRNOTAVAIL => return error.AddressUnavailable, |
| 9782 | 10144 | .ENOTSOCK => |err| return wsaErrorBug(err), |
| ... | ... | @@ -9886,7 +10248,7 @@ fn openSocketWsa( |
| 9886 | 10248 | const mode = posixSocketMode(options.mode); |
| 9887 | 10249 | const protocol = posixProtocol(options.protocol); |
| 9888 | 10250 | const flags: u32 = ws2_32.WSA_FLAG_OVERLAPPED | ws2_32.WSA_FLAG_NO_HANDLE_INHERIT; |
| 9889 | | const syscall: Syscall = try .start(); |
| 10251 | var syscall: Syscall = try .start(); |
| 9890 | 10252 | while (true) { |
| 9891 | 10253 | const rc = ws2_32.WSASocketW(family, @bitCast(mode), @bitCast(protocol), null, 0, flags); |
| 9892 | 10254 | if (rc != ws2_32.INVALID_SOCKET) { |
| ... | ... | @@ -9894,19 +10256,19 @@ fn openSocketWsa( |
| 9894 | 10256 | return rc; |
| 9895 | 10257 | } |
| 9896 | 10258 | switch (ws2_32.WSAGetLastError()) { |
| 9897 | | .EINTR => { |
| 10259 | .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { |
| 9898 | 10260 | try syscall.checkCancel(); |
| 9899 | 10261 | continue; |
| 9900 | 10262 | }, |
| 9901 | 10263 | .NOTINITIALISED => { |
| 10264 | syscall.finish(); |
| 9902 | 10265 | try initializeWsa(t); |
| 9903 | | try syscall.checkCancel(); |
| 10266 | syscall = try .start(); |
| 9904 | 10267 | continue; |
| 9905 | 10268 | }, |
| 9906 | 10269 | else => |e| { |
| 9907 | 10270 | syscall.finish(); |
| 9908 | 10271 | switch (e) { |
| 9909 | | .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, |
| 9910 | 10272 | .EAFNOSUPPORT => return error.AddressFamilyUnsupported, |
| 9911 | 10273 | .EMFILE => return error.ProcessFdQuotaExceeded, |
| 9912 | 10274 | .ENOBUFS => return error.SystemResources, |
| ... | ... | @@ -9984,7 +10346,7 @@ fn netAcceptWindows(userdata: ?*anyopaque, listen_handle: net.Socket.Handle) net |
| 9984 | 10346 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 9985 | 10347 | var storage: WsaAddress = undefined; |
| 9986 | 10348 | var addr_len: i32 = @sizeOf(WsaAddress); |
| 9987 | | const syscall: Syscall = try .start(); |
| 10349 | var syscall: Syscall = try .start(); |
| 9988 | 10350 | while (true) { |
| 9989 | 10351 | const rc = ws2_32.accept(listen_handle, &storage.any, &addr_len); |
| 9990 | 10352 | if (rc != ws2_32.INVALID_SOCKET) { |
| ... | ... | @@ -9995,19 +10357,19 @@ fn netAcceptWindows(userdata: ?*anyopaque, listen_handle: net.Socket.Handle) net |
| 9995 | 10357 | } }; |
| 9996 | 10358 | } |
| 9997 | 10359 | switch (ws2_32.WSAGetLastError()) { |
| 9998 | | .EINTR => { |
| 10360 | .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { |
| 9999 | 10361 | try syscall.checkCancel(); |
| 10000 | 10362 | continue; |
| 10001 | 10363 | }, |
| 10002 | 10364 | .NOTINITIALISED => { |
| 10365 | syscall.finish(); |
| 10003 | 10366 | try initializeWsa(t); |
| 10004 | | try syscall.checkCancel(); |
| 10367 | syscall = try .start(); |
| 10005 | 10368 | continue; |
| 10006 | 10369 | }, |
| 10007 | 10370 | else => |e| { |
| 10008 | 10371 | syscall.finish(); |
| 10009 | 10372 | switch (e) { |
| 10010 | | .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, |
| 10011 | 10373 | .ECONNRESET => return error.ConnectionAborted, |
| 10012 | 10374 | .EFAULT => |err| return wsaErrorBug(err), |
| 10013 | 10375 | .ENOTSOCK => |err| return wsaErrorBug(err), |
| ... | ... | @@ -10141,48 +10503,41 @@ fn netReadWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, data: [][]u8 |
| 10141 | 10503 | break :b bufs; |
| 10142 | 10504 | }; |
| 10143 | 10505 | |
| 10506 | var syscall: Syscall = try .start(); |
| 10144 | 10507 | while (true) { |
| 10145 | | try Thread.checkCancel(); |
| 10146 | | |
| 10147 | 10508 | var flags: u32 = 0; |
| 10148 | | var overlapped: windows.OVERLAPPED = std.mem.zeroes(windows.OVERLAPPED); |
| 10149 | 10509 | 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; |
| 10167 | 10519 | }, |
| 10168 | | else => |err| err, |
| 10169 | | }; |
| 10170 | | switch (wsa_error) { |
| 10171 | | .EINTR => continue, |
| 10172 | | .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, |
| 10173 | 10520 | .NOTINITIALISED => { |
| 10521 | syscall.finish(); |
| 10174 | 10522 | try initializeWsa(t); |
| 10523 | syscall = try .start(); |
| 10175 | 10524 | continue; |
| 10176 | 10525 | }, |
| 10177 | 10526 | |
| 10178 | | .ECONNRESET => return error.ConnectionResetByPeer, |
| 10527 | .ECONNRESET => return syscall.fail(error.ConnectionResetByPeer), |
| 10528 | .ENETDOWN => return syscall.fail(error.NetworkDown), |
| 10529 | .ENETRESET => return syscall.fail(error.ConnectionResetByPeer), |
| 10530 | .ENOTCONN => return syscall.fail(error.SocketUnconnected), |
| 10179 | 10531 | .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 | }, |
| 10186 | 10541 | } |
| 10187 | 10542 | } |
| 10188 | 10543 | } |
| ... | ... | @@ -10269,7 +10624,7 @@ fn netSendOne( |
| 10269 | 10624 | .controllen = @intCast(message.control.len), |
| 10270 | 10625 | .flags = 0, |
| 10271 | 10626 | }; |
| 10272 | | const syscall: Syscall = try .start(); |
| 10627 | var syscall: Syscall = try .start(); |
| 10273 | 10628 | while (true) { |
| 10274 | 10629 | const rc = posix.system.sendmsg(handle, &msg, flags); |
| 10275 | 10630 | if (is_windows) { |
| ... | ... | @@ -10279,19 +10634,19 @@ fn netSendOne( |
| 10279 | 10634 | return; |
| 10280 | 10635 | } |
| 10281 | 10636 | switch (ws2_32.WSAGetLastError()) { |
| 10282 | | .EINTR => { |
| 10637 | .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { |
| 10283 | 10638 | try syscall.checkCancel(); |
| 10284 | 10639 | continue; |
| 10285 | 10640 | }, |
| 10286 | 10641 | .NOTINITIALISED => { |
| 10642 | syscall.finish(); |
| 10287 | 10643 | try initializeWsa(t); |
| 10288 | | try syscall.checkCancel(); |
| 10644 | syscall = try .start(); |
| 10289 | 10645 | continue; |
| 10290 | 10646 | }, |
| 10291 | 10647 | else => |e| { |
| 10292 | 10648 | syscall.finish(); |
| 10293 | 10649 | switch (e) { |
| 10294 | | .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, |
| 10295 | 10650 | .EACCES => return error.AccessDenied, |
| 10296 | 10651 | .EADDRNOTAVAIL => return error.AddressUnavailable, |
| 10297 | 10652 | .ECONNRESET => return error.ConnectionResetByPeer, |
| ... | ... | @@ -10729,49 +11084,44 @@ fn netWriteWindows( |
| 10729 | 11084 | }, |
| 10730 | 11085 | }; |
| 10731 | 11086 | |
| 11087 | var syscall: Syscall = try .start(); |
| 10732 | 11088 | while (true) { |
| 10733 | | try Thread.checkCancel(); |
| 10734 | | |
| 10735 | 11089 | 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; |
| 10754 | 11100 | }, |
| 10755 | | else => |err| err, |
| 10756 | | }; |
| 10757 | | switch (wsa_error) { |
| 10758 | | .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => continue, |
| 10759 | 11101 | .NOTINITIALISED => { |
| 11102 | syscall.finish(); |
| 10760 | 11103 | try initializeWsa(t); |
| 11104 | syscall = try .start(); |
| 10761 | 11105 | continue; |
| 10762 | 11106 | }, |
| 10763 | 11107 | |
| 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 | }, |
| 10775 | 11125 | } |
| 10776 | 11126 | } |
| 10777 | 11127 | } |
| ... | ... | @@ -10872,7 +11222,6 @@ fn netShutdownPosix(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.S |
| 10872 | 11222 | fn netShutdownWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.ShutdownHow) net.ShutdownError!void { |
| 10873 | 11223 | if (!have_networking) return error.NetworkDown; |
| 10874 | 11224 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 10875 | | const current_thread = Thread.getCurrent(t); |
| 10876 | 11225 | |
| 10877 | 11226 | const wsa_how: i32 = switch (how) { |
| 10878 | 11227 | .recv => ws2_32.SD_RECEIVE, |
| ... | ... | @@ -10880,27 +11229,27 @@ fn netShutdownWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net |
| 10880 | 11229 | .both => ws2_32.SD_BOTH, |
| 10881 | 11230 | }; |
| 10882 | 11231 | |
| 10883 | | try current_thread.beginSyscall(); |
| 11232 | var syscall: Syscall = try .start(); |
| 10884 | 11233 | while (true) { |
| 10885 | 11234 | const rc = ws2_32.shutdown(handle, wsa_how); |
| 10886 | 11235 | if (rc != ws2_32.SOCKET_ERROR) { |
| 10887 | | current_thread.endSyscall(); |
| 11236 | syscall.finish(); |
| 10888 | 11237 | return; |
| 10889 | 11238 | } |
| 10890 | 11239 | switch (ws2_32.WSAGetLastError()) { |
| 10891 | | .EINTR => { |
| 10892 | | try current_thread.checkCancel(); |
| 11240 | .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { |
| 11241 | try syscall.checkCancel(); |
| 10893 | 11242 | continue; |
| 10894 | 11243 | }, |
| 10895 | 11244 | .NOTINITIALISED => { |
| 11245 | syscall.finish(); |
| 10896 | 11246 | try initializeWsa(t); |
| 10897 | | try current_thread.checkCancel(); |
| 11247 | syscall = try .start(); |
| 10898 | 11248 | continue; |
| 10899 | 11249 | }, |
| 10900 | 11250 | else => |e| { |
| 10901 | | current_thread.endSyscall(); |
| 11251 | syscall.finish(); |
| 10902 | 11252 | switch (e) { |
| 10903 | | .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, |
| 10904 | 11253 | .ECONNABORTED => return error.ConnectionAborted, |
| 10905 | 11254 | .ECONNRESET => return error.ConnectionResetByPeer, |
| 10906 | 11255 | .ENETDOWN => return error.NetworkDown, |
| ... | ... | @@ -11093,18 +11442,17 @@ fn netLookupFallible( |
| 11093 | 11442 | .provider = null, |
| 11094 | 11443 | .next = null, |
| 11095 | 11444 | }; |
| 11096 | | const cancel_handle: ?*windows.HANDLE = null; |
| 11097 | 11445 | var res: *ws2_32.ADDRINFOEXW = undefined; |
| 11098 | 11446 | const timeout: ?*ws2_32.timeval = null; |
| 11099 | 11447 | while (true) { |
| 11448 | // TODO: hook this up to cancelation with `Thread.Status.cancelation.blocked_windows_dns`. |
| 11449 | // See matching TODO in `Thread.cancelAwaitable`. |
| 11100 | 11450 | 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)); |
| 11104 | 11453 | switch (rc) { |
| 11105 | 11454 | @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, |
| 11108 | 11456 | .NOTINITIALISED => { |
| 11109 | 11457 | try initializeWsa(t); |
| 11110 | 11458 | continue; |
| ... | ... | @@ -11352,29 +11700,33 @@ fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) std.process.SetCurrentD |
| 11352 | 11700 | _ = t; |
| 11353 | 11701 | |
| 11354 | 11702 | if (is_windows) { |
| 11355 | | try Thread.checkCancel(); |
| 11356 | 11703 | var dir_path_buffer: [windows.PATH_MAX_WIDE]u16 = undefined; |
| 11357 | 11704 | // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks |
| 11705 | try Thread.checkCancel(); |
| 11358 | 11706 | const dir_path = try windows.GetFinalPathNameByHandle(dir.handle, .{}, &dir_path_buffer); |
| 11359 | 11707 | const path_len_bytes = std.math.cast(u16, dir_path.len * 2) orelse return error.NameTooLong; |
| 11360 | | try Thread.checkCancel(); |
| 11361 | 11708 | var nt_name: windows.UNICODE_STRING = .{ |
| 11362 | 11709 | .Length = path_len_bytes, |
| 11363 | 11710 | .MaximumLength = path_len_bytes, |
| 11364 | 11711 | .Buffer = @constCast(dir_path.ptr), |
| 11365 | 11712 | }; |
| 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 | }; |
| 11378 | 11730 | } |
| 11379 | 11731 | |
| 11380 | 11732 | if (dir.handle == posix.AT.FDCWD) return; |
| ... | ... | @@ -12185,391 +12537,6 @@ fn initializeWsa(t: *Threaded) error{ NetworkDown, Canceled }!void { |
| 12185 | 12537 | |
| 12186 | 12538 | fn doNothingSignalHandler(_: posix.SIG) callconv(.c) void {} |
| 12187 | 12539 | |
| 12188 | | const 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 | | |
| 12573 | 12540 | fn scanEnviron(t: *Threaded) void { |
| 12574 | 12541 | t.mutex.lock(); |
| 12575 | 12542 | defer t.mutex.unlock(); |
| ... | ... | @@ -12688,3 +12655,459 @@ fn scanEnviron(t: *Threaded) void { |
| 12688 | 12655 | test { |
| 12689 | 12656 | _ = @import("Threaded/test.zig"); |
| 12690 | 12657 | } |
| 12658 | |
| 12659 | const 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 | }; |
| 12665 | const 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 | |
| 12685 | const 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 | }; |
| 12927 | const 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. |
| 13002 | fn 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 | |
| 13071 | const 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. |
| 13077 | fn 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 | } |