| ... | @@ -105,12 +105,7 @@ pub const Environ = struct { | ... | @@ -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 { | 108 | pub const RobustCancel = enum { enabled, disabled }; |
| 109 | enabled, | | |
| 110 | disabled, | | |
| 111 | } else enum { | | |
| 112 | disabled, | | |
| 113 | }; | | |
| 114 | | 109 | |
| 115 | pub const Pid = if (native_os == .linux) enum(posix.pid_t) { | 110 | pub const Pid = if (native_os == .linux) enum(posix.pid_t) { |
| 116 | unknown = 0, | 111 | unknown = 0, |
| ... | @@ -514,13 +509,21 @@ const AwaitableId = enum(@Int(.unsigned, @bitSizeOf(usize) - 3)) { | ... | @@ -514,13 +509,21 @@ const AwaitableId = enum(@Int(.unsigned, @bitSizeOf(usize) - 3)) { |
| 514 | | 509 | |
| 515 | const Thread = struct { | 510 | const Thread = struct { |
| 516 | next: ?*Thread, | 511 | next: ?*Thread, |
| 517 | /// The value that needs to be passed to pthread_kill or tgkill in order to | 512 | |
| 518 | /// send a signal. | 513 | id: std.Thread.Id, |
| 519 | signalee_id: SignaleeId, | 514 | handle: Handle, |
| 520 | | 515 | |
| 521 | status: std.atomic.Value(Status), | 516 | status: std.atomic.Value(Status), |
| 522 | | 517 | |
| 523 | cancel_protection: Io.CancelProtection, | 518 | cancel_protection: Io.CancelProtection, |
| | 519 | /// Always released when `Status.cancelation` is set to `.parked`. |
| | 520 | futex_waiter: if (use_parking_futex) ?*parking_futex.Waiter else ?noreturn, |
| | 521 | |
| | 522 | const Handle = Handle: { |
| | 523 | if (std.Thread.use_pthreads) break :Handle std.c.pthread_t; |
| | 524 | if (builtin.target.os.tag == .windows) break :Handle windows.HANDLE; |
| | 525 | break :Handle void; |
| | 526 | }; |
| 524 | | 527 | |
| 525 | const Status = packed struct(usize) { | 528 | const Status = packed struct(usize) { |
| 526 | /// The specific values of these enum fields are chosen to simplify the implementation of | 529 | /// The specific values of these enum fields are chosen to simplify the implementation of |
| ... | @@ -531,7 +534,7 @@ const Thread = struct { | ... | @@ -531,7 +534,7 @@ const Thread = struct { |
| 531 | none = 0b000, | 534 | none = 0b000, |
| 532 | | 535 | |
| 533 | /// The thread is parked in a cancelable futex wait or sleep. | 536 | /// The thread is parked in a cancelable futex wait or sleep. |
| 534 | /// Only applicable on Windows, NetBSD, and Illumos. | 537 | /// Only applicable if `use_parking_futex` or `use_parking_sleep`. |
| 535 | /// To request cancelation, set the status to `.canceling` and unpark the thread. | 538 | /// To request cancelation, set the status to `.canceling` and unpark the thread. |
| 536 | /// To unpark for another reason (futex wake), set the status to `.none` and unpark the thread. | 539 | /// To unpark for another reason (futex wake), set the status to `.none` and unpark the thread. |
| 537 | parked = 0b001, | 540 | parked = 0b001, |
| ... | @@ -540,8 +543,8 @@ const Thread = struct { | ... | @@ -540,8 +543,8 @@ const Thread = struct { |
| 540 | /// To request cancelation, set the status to `.blocked_canceling` and repeatedly interrupt the system call until the status changes. | 543 | /// To request cancelation, set the status to `.blocked_canceling` and repeatedly interrupt the system call until the status changes. |
| 541 | blocked = 0b011, | 544 | blocked = 0b011, |
| 542 | | 545 | |
| 543 | /// Windows-only: the thread is blocked on a DNS query. | 546 | /// Windows-only: the thread is blocked in a call to `GetAddrInfoExW`. |
| 544 | /// To request cancelation, set the status to `.canceling` and call `DnsCancelQuery`. | 547 | /// To request cancelation, set the status to `.canceling` and call `GetAddrInfoExCancel`. |
| 545 | blocked_windows_dns = 0b010, | 548 | blocked_windows_dns = 0b010, |
| 546 | | 549 | |
| 547 | /// The thread has an outstanding cancelation request but is not in a cancelable operation. | 550 | /// The thread has an outstanding cancelation request but is not in a cancelable operation. |
| ... | @@ -597,10 +600,6 @@ const Thread = struct { | ... | @@ -597,10 +600,6 @@ const Thread = struct { |
| 597 | } | 600 | } |
| 598 | } | 601 | } |
| 599 | | 602 | |
| 600 | fn currentSignaleeId() SignaleeId { | | |
| 601 | return if (std.Thread.use_pthreads) std.c.pthread_self() else std.Thread.getCurrentId(); | | |
| 602 | } | | |
| 603 | | | |
| 604 | fn futexWaitUncancelable(ptr: *const u32, expect: u32, timeout_ns: ?u64) void { | 603 | fn futexWaitUncancelable(ptr: *const u32, expect: u32, timeout_ns: ?u64) void { |
| 605 | return Thread.futexWaitInner(ptr, expect, true, timeout_ns) catch unreachable; | 604 | return Thread.futexWaitInner(ptr, expect, true, timeout_ns) catch unreachable; |
| 606 | } | 605 | } |
| ... | @@ -614,8 +613,19 @@ const Thread = struct { | ... | @@ -614,8 +613,19 @@ const Thread = struct { |
| 614 | | 613 | |
| 615 | if (builtin.single_threaded) unreachable; // nobody would ever wake us | 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 | comptime assert(builtin.cpu.has(.wasm, .atomics)); | 627 | comptime assert(builtin.cpu.has(.wasm, .atomics)); |
| | 628 | // TODO implement cancelation for WASM futex waits by signaling the futex |
| 619 | if (!uncancelable) try Thread.checkCancel(); | 629 | if (!uncancelable) try Thread.checkCancel(); |
| 620 | const to: i64 = if (timeout_ns) |ns| ns else -1; | 630 | const to: i64 = if (timeout_ns) |ns| ns else -1; |
| 621 | const signed_expect: i32 = @bitCast(expect); | 631 | const signed_expect: i32 = @bitCast(expect); |
| ... | @@ -689,24 +699,6 @@ const Thread = struct { | ... | @@ -689,24 +699,6 @@ const Thread = struct { |
| 689 | else => recoverableOsBugDetected(), | 699 | else => recoverableOsBugDetected(), |
| 690 | } | 700 | } |
| 691 | }, | 701 | }, |
| 692 | .windows => { | | |
| 693 | var timeout_value: windows.LARGE_INTEGER = undefined; | | |
| 694 | var timeout_ptr: ?*const windows.LARGE_INTEGER = null; | | |
| 695 | // NTDLL functions work with time in units of 100 nanoseconds. | | |
| 696 | // Positive values are absolute deadlines while negative values are relative durations. | | |
| 697 | if (timeout_ns) |delay| { | | |
| 698 | timeout_value = @as(windows.LARGE_INTEGER, @intCast(delay / 100)); | | |
| 699 | timeout_value = -timeout_value; | | |
| 700 | timeout_ptr = &timeout_value; | | |
| 701 | } | | |
| 702 | if (!uncancelable) try Thread.checkCancel(); | | |
| 703 | switch (windows.ntdll.RtlWaitOnAddress(ptr, &expect, @sizeOf(@TypeOf(expect)), timeout_ptr)) { | | |
| 704 | .SUCCESS => {}, | | |
| 705 | .CANCELLED => {}, | | |
| 706 | .TIMEOUT => {}, // timeout | | |
| 707 | else => recoverableOsBugDetected(), | | |
| 708 | } | | |
| 709 | }, | | |
| 710 | .freebsd => { | 702 | .freebsd => { |
| 711 | const flags = @intFromEnum(std.c.UMTX_OP.WAIT_UINT_PRIVATE); | 703 | const flags = @intFromEnum(std.c.UMTX_OP.WAIT_UINT_PRIVATE); |
| 712 | var tm_size: usize = 0; | 704 | var tm_size: usize = 0; |
| ... | @@ -738,7 +730,7 @@ const Thread = struct { | ... | @@ -738,7 +730,7 @@ const Thread = struct { |
| 738 | tm_ptr = &tm; | 730 | tm_ptr = &tm; |
| 739 | tm = timestampToPosix(ns); | 731 | tm = timestampToPosix(ns); |
| 740 | } | 732 | } |
| 741 | if (thread) |t| try t.beginSyscall(); | 733 | const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start(); |
| 742 | const rc = std.c.futex( | 734 | const rc = std.c.futex( |
| 743 | ptr, | 735 | ptr, |
| 744 | std.c.FUTEX.WAIT | std.c.FUTEX.PRIVATE_FLAG, | 736 | std.c.FUTEX.WAIT | std.c.FUTEX.PRIVATE_FLAG, |
| ... | @@ -746,7 +738,7 @@ const Thread = struct { | ... | @@ -746,7 +738,7 @@ const Thread = struct { |
| 746 | tm_ptr, | 738 | tm_ptr, |
| 747 | null, // uaddr2 is ignored | 739 | null, // uaddr2 is ignored |
| 748 | ); | 740 | ); |
| 749 | if (thread) |t| t.endSyscall(); | 741 | syscall.finish(); |
| 750 | if (is_debug) switch (posix.errno(rc)) { | 742 | if (is_debug) switch (posix.errno(rc)) { |
| 751 | .SUCCESS => {}, | 743 | .SUCCESS => {}, |
| 752 | .NOSYS => unreachable, // constant op known good value | 744 | .NOSYS => unreachable, // constant op known good value |
| ... | @@ -765,9 +757,9 @@ const Thread = struct { | ... | @@ -765,9 +757,9 @@ const Thread = struct { |
| 765 | } else { | 757 | } else { |
| 766 | timeout_us = 0; | 758 | timeout_us = 0; |
| 767 | } | 759 | } |
| 768 | if (thread) |t| try t.beginSyscall(); | 760 | const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start(); |
| 769 | const rc = std.c.umtx_sleep(@ptrCast(ptr), @bitCast(expect), timeout_us); | 761 | const rc = std.c.umtx_sleep(@ptrCast(ptr), @bitCast(expect), timeout_us); |
| 770 | if (thread) |t| t.endSyscall(); | 762 | syscall.finish(); |
| 771 | if (is_debug) switch (std.posix.errno(rc)) { | 763 | if (is_debug) switch (std.posix.errno(rc)) { |
| 772 | .SUCCESS => {}, | 764 | .SUCCESS => {}, |
| 773 | .BUSY => {}, // ptr != expect | 765 | .BUSY => {}, // ptr != expect |
| ... | @@ -777,14 +769,7 @@ const Thread = struct { | ... | @@ -777,14 +769,7 @@ const Thread = struct { |
| 777 | else => unreachable, | 769 | else => unreachable, |
| 778 | }; | 770 | }; |
| 779 | }, | 771 | }, |
| 780 | else => if (std.Thread.use_pthreads) { | 772 | else => @compileError("unimplemented: futexWait"), |
| 781 | // TODO integrate the following function being called with robust cancelation. | | |
| 782 | return pthreads_futex.wait(ptr, expect, timeout_ns) catch |err| switch (err) { | | |
| 783 | error.Timeout => {}, | | |
| 784 | }; | | |
| 785 | } else { | | |
| 786 | @compileError("unimplemented: futexWait"); | | |
| 787 | }, | | |
| 788 | } | 773 | } |
| 789 | } | 774 | } |
| 790 | | 775 | |
| ... | @@ -794,7 +779,9 @@ const Thread = struct { | ... | @@ -794,7 +779,9 @@ const Thread = struct { |
| 794 | | 779 | |
| 795 | if (builtin.single_threaded) return; // nothing to wake up | 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 | comptime assert(builtin.cpu.has(.wasm, .atomics)); | 785 | comptime assert(builtin.cpu.has(.wasm, .atomics)); |
| 799 | const woken_count = asm volatile ( | 786 | const woken_count = asm volatile ( |
| 800 | \\local.get %[ptr] | 787 | \\local.get %[ptr] |
| ... | @@ -839,12 +826,6 @@ const Thread = struct { | ... | @@ -839,12 +826,6 @@ const Thread = struct { |
| 839 | } | 826 | } |
| 840 | } | 827 | } |
| 841 | }, | 828 | }, |
| 842 | .windows => { | | |
| 843 | switch (max_waiters) { | | |
| 844 | 1 => windows.ntdll.RtlWakeAddressSingle(ptr), | | |
| 845 | else => windows.ntdll.RtlWakeAddressAll(ptr), | | |
| 846 | } | | |
| 847 | }, | | |
| 848 | .freebsd => { | 829 | .freebsd => { |
| 849 | const rc = std.c._umtx_op( | 830 | const rc = std.c._umtx_op( |
| 850 | @intFromPtr(ptr), | 831 | @intFromPtr(ptr), |
| ... | @@ -877,11 +858,7 @@ const Thread = struct { | ... | @@ -877,11 +858,7 @@ const Thread = struct { |
| 877 | @min(max_waiters, std.math.maxInt(c_int)), | 858 | @min(max_waiters, std.math.maxInt(c_int)), |
| 878 | ); | 859 | ); |
| 879 | }, | 860 | }, |
| 880 | else => if (std.Thread.use_pthreads) { | 861 | else => @compileError("unimplemented: futexWake"), |
| 881 | return pthreads_futex.wake(ptr, max_waiters); | | |
| 882 | } else { | | |
| 883 | @compileError("unimplemented: futexWake"); | | |
| 884 | }, | | |
| 885 | } | 862 | } |
| 886 | } | 863 | } |
| 887 | | 864 | |
| ... | @@ -905,10 +882,14 @@ const Thread = struct { | ... | @@ -905,10 +882,14 @@ const Thread = struct { |
| 905 | .parked => thread.status.cmpxchgWeak( | 882 | .parked => thread.status.cmpxchgWeak( |
| 906 | .{ .cancelation = .parked, .awaitable = awaitable }, | 883 | .{ .cancelation = .parked, .awaitable = awaitable }, |
| 907 | .{ .cancelation = .canceling, .awaitable = awaitable }, | 884 | .{ .cancelation = .canceling, .awaitable = awaitable }, |
| 908 | .monotonic, | 885 | .acquire, // acquire `thread.futex_waiter` |
| 909 | .monotonic, | 886 | .monotonic, |
| 910 | ) orelse { | 887 | ) orelse { |
| 911 | if (true) @panic("MLUGG TODO: unpark thread"); | 888 | if (!use_parking_futex and !use_parking_sleep) unreachable; |
| | 889 | if (thread.futex_waiter) |futex_waiter| { |
| | 890 | parking_futex.removeCanceledWaiter(futex_waiter); |
| | 891 | } |
| | 892 | unpark(&.{thread.id}, null); |
| 912 | return false; | 893 | return false; |
| 913 | }, | 894 | }, |
| 914 | | 895 | |
| ... | @@ -924,7 +905,15 @@ const Thread = struct { | ... | @@ -924,7 +905,15 @@ const Thread = struct { |
| 924 | .{ .cancelation = .canceling, .awaitable = awaitable }, | 905 | .{ .cancelation = .canceling, .awaitable = awaitable }, |
| 925 | .monotonic, | 906 | .monotonic, |
| 926 | .monotonic, | 907 | .monotonic, |
| 927 | ) orelse return false, | 908 | ) orelse { |
| | 909 | if (builtin.target.os.tag != .windows) unreachable; |
| | 910 | if (true) { |
| | 911 | // TODO: cancel Windows DNS queries. This code path is currently impossible |
| | 912 | // as `netLookupFallible` doesn't actually use `.blocked_windows_dns` yet. |
| | 913 | unreachable; |
| | 914 | } |
| | 915 | return false; |
| | 916 | }, |
| 928 | | 917 | |
| 929 | .canceling, .canceled => { | 918 | .canceling, .canceled => { |
| 930 | // This can happen when the task start raced with the cancelation, so the thread | 919 | // This can happen when the task start raced with the cancelation, so the thread |
| ... | @@ -951,26 +940,38 @@ const Thread = struct { | ... | @@ -951,26 +940,38 @@ const Thread = struct { |
| 951 | const bad_status: Status = .{ .cancelation = .blocked_canceling, .awaitable = awaitable }; | 940 | const bad_status: Status = .{ .cancelation = .blocked_canceling, .awaitable = awaitable }; |
| 952 | if (thread.status.load(.monotonic) != bad_status) return false; | 941 | if (thread.status.load(.monotonic) != bad_status) return false; |
| 953 | | 942 | |
| 954 | // The thread ID can be read non-atomically because it never changes and was released by the | 943 | // The thread ID and/or handle can be read non-atomically because they never change and were |
| 955 | // store that made `thread` available to us. | 944 | // released by the store that made `thread` available to us. |
| 956 | const signalee_id = thread.signalee_id; | | |
| 957 | | 945 | |
| 958 | if (std.Thread.use_pthreads) { | 946 | if (std.Thread.use_pthreads) { |
| 959 | if (std.c.pthread_kill(signalee_id, .IO) != 0) return false; | 947 | return switch (std.c.pthread_kill(thread.handle, .IO)) { |
| 960 | } else if (native_os == .linux) { | 948 | 0 => true, |
| 961 | const pid: posix.pid_t = pid: { | 949 | else => false, |
| 962 | const cached_pid = @atomicLoad(Pid, &t.pid, .monotonic); | | |
| 963 | if (cached_pid != .unknown) break :pid @intFromEnum(cached_pid); | | |
| 964 | const pid = std.os.linux.getpid(); | | |
| 965 | @atomicStore(Pid, &t.pid, @enumFromInt(pid), .monotonic); | | |
| 966 | break :pid pid; | | |
| 967 | }; | 950 | }; |
| 968 | if (std.os.linux.tgkill(pid, @bitCast(signalee_id), .IO) != 0) return false; | 951 | } else switch (builtin.target.os.tag) { |
| 969 | } else { | 952 | .linux => { |
| 970 | @compileError("MLUGG TODO"); | 953 | const pid: posix.pid_t = pid: { |
| | 954 | const cached_pid = @atomicLoad(Pid, &t.pid, .monotonic); |
| | 955 | if (cached_pid != .unknown) break :pid @intFromEnum(cached_pid); |
| | 956 | const pid = std.os.linux.getpid(); |
| | 957 | @atomicStore(Pid, &t.pid, @enumFromInt(pid), .monotonic); |
| | 958 | break :pid pid; |
| | 959 | }; |
| | 960 | return switch (std.os.linux.tgkill(pid, @bitCast(thread.id), .IO)) { |
| | 961 | 0 => true, |
| | 962 | else => false, |
| | 963 | }; |
| | 964 | }, |
| | 965 | .windows => { |
| | 966 | var iosb: windows.IO_STATUS_BLOCK = undefined; |
| | 967 | return switch (windows.ntdll.NtCancelSynchronousIoFile(thread.handle, null, &iosb)) { |
| | 968 | .NOT_FOUND => true, // this might mean the operation hasn't started yet |
| | 969 | .SUCCESS => false, // the OS confirmed that our cancelation worked |
| | 970 | else => false, |
| | 971 | }; |
| | 972 | }, |
| | 973 | else => return false, |
| 971 | } | 974 | } |
| 972 | | | |
| 973 | return true; | | |
| 974 | } | 975 | } |
| 975 | | 976 | |
| 976 | /// Like a `*Thread`, but 2 bits smaller than a pointer (because the LSBs are always 0 due to | 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,6 +1070,18 @@ const Syscall = struct { |
| 1069 | s.finish(); | 1070 | s.finish(); |
| 1070 | return posix.unexpectedErrno(err); | 1071 | return posix.unexpectedErrno(err); |
| 1071 | } | 1072 | } |
| | 1073 | /// Convenience wrapper which calls `finish`, then calls `windows.statusBug`. |
| | 1074 | fn ntstatusBug(s: Syscall, status: windows.NTSTATUS) Io.UnexpectedError { |
| | 1075 | @branchHint(.cold); |
| | 1076 | s.finish(); |
| | 1077 | return windows.statusBug(status); |
| | 1078 | } |
| | 1079 | /// Convenience wrapper which calls `finish`, then calls `windows.unexpectedStatus`. |
| | 1080 | fn unexpectedNtstatus(s: Syscall, status: windows.NTSTATUS) Io.UnexpectedError { |
| | 1081 | @branchHint(.cold); |
| | 1082 | s.finish(); |
| | 1083 | return windows.unexpectedStatus(status); |
| | 1084 | } |
| 1072 | }; | 1085 | }; |
| 1073 | | 1086 | |
| 1074 | const max_iovecs_len = 8; | 1087 | const max_iovecs_len = 8; |
| ... | @@ -1233,15 +1246,45 @@ fn join(t: *Threaded) void { | ... | @@ -1233,15 +1246,45 @@ fn join(t: *Threaded) void { |
| 1233 | fn worker(t: *Threaded) void { | 1246 | fn worker(t: *Threaded) void { |
| 1234 | var thread: Thread = .{ | 1247 | var thread: Thread = .{ |
| 1235 | .next = undefined, | 1248 | .next = undefined, |
| 1236 | .signalee_id = Thread.currentSignaleeId(), | 1249 | .id = std.Thread.getCurrentId(), |
| | 1250 | .handle = handle: { |
| | 1251 | if (std.Thread.use_pthreads) break :handle std.c.pthread_self(); |
| | 1252 | if (builtin.target.os.tag == .windows) break :handle undefined; // populated below |
| | 1253 | }, |
| 1237 | .status = .init(.{ | 1254 | .status = .init(.{ |
| 1238 | .cancelation = .none, | 1255 | .cancelation = .none, |
| 1239 | .awaitable = .null, | 1256 | .awaitable = .null, |
| 1240 | }), | 1257 | }), |
| 1241 | .cancel_protection = .unblocked, | 1258 | .cancel_protection = .unblocked, |
| | 1259 | .futex_waiter = undefined, |
| 1242 | }; | 1260 | }; |
| 1243 | Thread.current = &thread; | 1261 | Thread.current = &thread; |
| 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 | var head = t.worker_threads.load(.monotonic); | 1289 | var head = t.worker_threads.load(.monotonic); |
| 1247 | while (true) { | 1290 | while (true) { |
| ... | @@ -2127,26 +2170,34 @@ fn dirCreateDirWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permi | ... | @@ -2127,26 +2170,34 @@ fn dirCreateDirWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permi |
| 2127 | fn dirCreateDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void { | 2170 | fn dirCreateDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void { |
| 2128 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 2171 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2129 | _ = t; | 2172 | _ = t; |
| 2130 | try Thread.checkCancel(); | | |
| 2131 | | 2173 | |
| 2132 | const sub_path_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path); | 2174 | const sub_path_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path); |
| 2133 | _ = permissions; // TODO use this value | 2175 | _ = permissions; // TODO use this value |
| 2134 | const sub_dir_handle = windows.OpenFile(sub_path_w.span(), .{ | 2176 | |
| 2135 | .dir = dir.handle, | 2177 | const syscall: Syscall = try .start(); |
| 2136 | .access_mask = .{ | 2178 | const sub_dir_handle = while (true) { |
| 2137 | .GENERIC = .{ .READ = true }, | 2179 | break windows.OpenFile(sub_path_w.span(), .{ |
| 2138 | .STANDARD = .{ .SYNCHRONIZE = true }, | 2180 | .dir = dir.handle, |
| 2139 | }, | 2181 | .access_mask = .{ |
| 2140 | .creation = .CREATE, | 2182 | .GENERIC = .{ .READ = true }, |
| 2141 | .filter = .dir_only, | 2183 | .STANDARD = .{ .SYNCHRONIZE = true }, |
| 2142 | }) catch |err| switch (err) { | 2184 | }, |
| 2143 | error.IsDir => return error.Unexpected, | 2185 | .creation = .CREATE, |
| 2144 | error.PipeBusy => return error.Unexpected, | 2186 | .filter = .dir_only, |
| 2145 | error.NoDevice => return error.Unexpected, | 2187 | }) catch |err| switch (err) { |
| 2146 | error.WouldBlock => return error.Unexpected, | 2188 | error.IsDir => return syscall.fail(error.Unexpected), |
| 2147 | error.AntivirusInterference => return error.Unexpected, | 2189 | error.PipeBusy => return syscall.fail(error.Unexpected), |
| 2148 | else => |e| return e, | 2190 | error.NoDevice => return syscall.fail(error.Unexpected), |
| | 2191 | error.WouldBlock => return syscall.fail(error.Unexpected), |
| | 2192 | error.AntivirusInterference => return syscall.fail(error.Unexpected), |
| | 2193 | error.OperationCanceled => { |
| | 2194 | try syscall.checkCancel(); |
| | 2195 | continue; |
| | 2196 | }, |
| | 2197 | else => |e| return syscall.fail(e), |
| | 2198 | }; |
| 2149 | }; | 2199 | }; |
| | 2200 | syscall.finish(); |
| 2150 | windows.CloseHandle(sub_dir_handle); | 2201 | windows.CloseHandle(sub_dir_handle); |
| 2151 | } | 2202 | } |
| 2152 | | 2203 | |
| ... | @@ -2225,9 +2276,7 @@ fn dirCreateDirPathOpenWindows( | ... | @@ -2225,9 +2276,7 @@ fn dirCreateDirPathOpenWindows( |
| 2225 | .path = sub_path, | 2276 | .path = sub_path, |
| 2226 | }; | 2277 | }; |
| 2227 | | 2278 | |
| 2228 | while (true) { | 2279 | components: while (true) { |
| 2229 | try Thread.checkCancel(); | | |
| 2230 | | | |
| 2231 | const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, component.path); | 2280 | const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, component.path); |
| 2232 | const sub_path_w = sub_path_w_array.span(); | 2281 | const sub_path_w = sub_path_w_array.span(); |
| 2233 | const is_last = it.peekNext() == null; | 2282 | const is_last = it.peekNext() == null; |
| ... | @@ -2242,7 +2291,9 @@ fn dirCreateDirPathOpenWindows( | ... | @@ -2242,7 +2291,9 @@ fn dirCreateDirPathOpenWindows( |
| 2242 | .Buffer = @constCast(sub_path_w.ptr), | 2291 | .Buffer = @constCast(sub_path_w.ptr), |
| 2243 | }; | 2292 | }; |
| 2244 | var io_status_block: w.IO_STATUS_BLOCK = undefined; | 2293 | var io_status_block: w.IO_STATUS_BLOCK = undefined; |
| 2245 | const rc = w.ntdll.NtCreateFile( | 2294 | |
| | 2295 | const syscall: Syscall = try .start(); |
| | 2296 | while (true) switch (w.ntdll.NtCreateFile( |
| 2246 | &result.handle, | 2297 | &result.handle, |
| 2247 | .{ | 2298 | .{ |
| 2248 | .SPECIFIC = .{ .FILE_DIRECTORY = .{ | 2299 | .SPECIFIC = .{ .FILE_DIRECTORY = .{ |
| ... | @@ -2277,16 +2328,20 @@ fn dirCreateDirPathOpenWindows( | ... | @@ -2277,16 +2328,20 @@ fn dirCreateDirPathOpenWindows( |
| 2277 | }, | 2328 | }, |
| 2278 | null, | 2329 | null, |
| 2279 | 0, | 2330 | 0, |
| 2280 | ); | 2331 | )) { |
| 2281 | | | |
| 2282 | switch (rc) { | | |
| 2283 | .SUCCESS => { | 2332 | .SUCCESS => { |
| | 2333 | syscall.finish(); |
| 2284 | component = it.next() orelse return result; | 2334 | component = it.next() orelse return result; |
| 2285 | w.CloseHandle(result.handle); | 2335 | w.CloseHandle(result.handle); |
| | 2336 | continue :components; |
| | 2337 | }, |
| | 2338 | .CANCELLED => { |
| | 2339 | try syscall.checkCancel(); |
| 2286 | continue; | 2340 | continue; |
| 2287 | }, | 2341 | }, |
| 2288 | .OBJECT_NAME_INVALID => return error.BadPathName, | 2342 | .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), |
| 2289 | .OBJECT_NAME_COLLISION => { | 2343 | .OBJECT_NAME_COLLISION => { |
| | 2344 | syscall.finish(); |
| 2290 | assert(!is_last); | 2345 | assert(!is_last); |
| 2291 | // stat the file and return an error if it's not a directory | 2346 | // stat the file and return an error if it's not a directory |
| 2292 | // this is important because otherwise a dangling symlink | 2347 | // this is important because otherwise a dangling symlink |
| ... | @@ -2297,23 +2352,24 @@ fn dirCreateDirPathOpenWindows( | ... | @@ -2297,23 +2352,24 @@ fn dirCreateDirPathOpenWindows( |
| 2297 | if (fstat.kind != .directory) return error.NotDir; | 2352 | if (fstat.kind != .directory) return error.NotDir; |
| 2298 | | 2353 | |
| 2299 | component = it.next().?; | 2354 | component = it.next().?; |
| 2300 | continue; | 2355 | continue :components; |
| 2301 | }, | 2356 | }, |
| 2302 | | 2357 | |
| 2303 | .OBJECT_NAME_NOT_FOUND, | 2358 | .OBJECT_NAME_NOT_FOUND, |
| 2304 | .OBJECT_PATH_NOT_FOUND, | 2359 | .OBJECT_PATH_NOT_FOUND, |
| 2305 | => { | 2360 | => { |
| | 2361 | syscall.finish(); |
| 2306 | component = it.previous() orelse return error.FileNotFound; | 2362 | component = it.previous() orelse return error.FileNotFound; |
| 2307 | continue; | 2363 | continue :components; |
| 2308 | }, | 2364 | }, |
| 2309 | | 2365 | |
| 2310 | .NOT_A_DIRECTORY => return error.NotDir, | 2366 | .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), |
| 2311 | // This can happen if the directory has 'List folder contents' permission set to 'Deny' | 2367 | // This can happen if the directory has 'List folder contents' permission set to 'Deny' |
| 2312 | // and the directory is trying to be opened for iteration. | 2368 | // and the directory is trying to be opened for iteration. |
| 2313 | .ACCESS_DENIED => return error.AccessDenied, | 2369 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| 2314 | .INVALID_PARAMETER => |err| return w.statusBug(err), | 2370 | .INVALID_PARAMETER => |s| return syscall.ntstatusBug(s), |
| 2315 | else => return w.unexpectedStatus(rc), | 2371 | else => |s| return syscall.unexpectedNtstatus(s), |
| 2316 | } | 2372 | }; |
| 2317 | } | 2373 | } |
| 2318 | } | 2374 | } |
| 2319 | | 2375 | |
| ... | @@ -2637,20 +2693,31 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { | ... | @@ -2637,20 +2693,31 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { |
| 2637 | fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { | 2693 | fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { |
| 2638 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 2694 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2639 | _ = t; | 2695 | _ = t; |
| 2640 | try Thread.checkCancel(); | | |
| 2641 | | 2696 | |
| 2642 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | 2697 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 2643 | var info: windows.FILE.ALL_INFORMATION = undefined; | 2698 | var info: windows.FILE.ALL_INFORMATION = undefined; |
| 2644 | const rc = windows.ntdll.NtQueryInformationFile(file.handle, &io_status_block, &info, @sizeOf(windows.FILE.ALL_INFORMATION), .All); | 2699 | { |
| 2645 | switch (rc) { | 2700 | const syscall: Syscall = try .start(); |
| 2646 | .SUCCESS => {}, | 2701 | while (true) switch (windows.ntdll.NtQueryInformationFile( |
| 2647 | // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer | 2702 | file.handle, |
| 2648 | // size provided. This is treated as success because the type of variable-length information that this would be relevant for | 2703 | &io_status_block, |
| 2649 | // (name, volume name, etc) we don't care about. | 2704 | &info, |
| 2650 | .BUFFER_OVERFLOW => {}, | 2705 | @sizeOf(windows.FILE.ALL_INFORMATION), |
| 2651 | .INVALID_PARAMETER => |err| return windows.statusBug(err), | 2706 | .All, |
| 2652 | .ACCESS_DENIED => return error.AccessDenied, | 2707 | )) { |
| 2653 | else => return windows.unexpectedStatus(rc), | 2708 | .SUCCESS => break syscall.finish(), |
| | 2709 | // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer |
| | 2710 | // size provided. This is treated as success because the type of variable-length information that this would be relevant for |
| | 2711 | // (name, volume name, etc) we don't care about. |
| | 2712 | .BUFFER_OVERFLOW => break syscall.finish(), |
| | 2713 | .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), |
| | 2714 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| | 2715 | .CANCELLED => { |
| | 2716 | try syscall.checkCancel(); |
| | 2717 | continue; |
| | 2718 | }, |
| | 2719 | else => |s| return syscall.unexpectedNtstatus(s), |
| | 2720 | }; |
| 2654 | } | 2721 | } |
| 2655 | return .{ | 2722 | return .{ |
| 2656 | .inode = info.InternalInformation.IndexNumber, | 2723 | .inode = info.InternalInformation.IndexNumber, |
| ... | @@ -2658,15 +2725,25 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { | ... | @@ -2658,15 +2725,25 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { |
| 2658 | .permissions = .default_file, | 2725 | .permissions = .default_file, |
| 2659 | .kind = if (info.BasicInformation.FileAttributes.REPARSE_POINT) reparse_point: { | 2726 | .kind = if (info.BasicInformation.FileAttributes.REPARSE_POINT) reparse_point: { |
| 2660 | var tag_info: windows.FILE.ATTRIBUTE_TAG_INFO = undefined; | 2727 | var tag_info: windows.FILE.ATTRIBUTE_TAG_INFO = undefined; |
| 2661 | const tag_rc = windows.ntdll.NtQueryInformationFile(file.handle, &io_status_block, &tag_info, @sizeOf(windows.FILE.ATTRIBUTE_TAG_INFO), .AttributeTag); | 2728 | const syscall: Syscall = try .start(); |
| 2662 | switch (tag_rc) { | 2729 | while (true) switch (windows.ntdll.NtQueryInformationFile( |
| 2663 | .SUCCESS => {}, | 2730 | file.handle, |
| | 2731 | &io_status_block, |
| | 2732 | &tag_info, |
| | 2733 | @sizeOf(windows.FILE.ATTRIBUTE_TAG_INFO), |
| | 2734 | .AttributeTag, |
| | 2735 | )) { |
| | 2736 | .SUCCESS => break syscall.finish(), |
| 2664 | // INFO_LENGTH_MISMATCH and ACCESS_DENIED are the only documented possible errors | 2737 | // INFO_LENGTH_MISMATCH and ACCESS_DENIED are the only documented possible errors |
| 2665 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/d295752f-ce89-4b98-8553-266d37c84f0e | 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), | 2739 | .INFO_LENGTH_MISMATCH => |err| return syscall.ntstatusBug(err), |
| 2667 | .ACCESS_DENIED => return error.AccessDenied, | 2740 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| 2668 | else => return windows.unexpectedStatus(rc), | 2741 | .CANCELLED => { |
| 2669 | } | 2742 | try syscall.checkCancel(); |
| | 2743 | continue; |
| | 2744 | }, |
| | 2745 | else => |s| return syscall.unexpectedNtstatus(s), |
| | 2746 | }; |
| 2670 | if (tag_info.ReparseTag.IsSurrogate) break :reparse_point .sym_link; | 2747 | if (tag_info.ReparseTag.IsSurrogate) break :reparse_point .sym_link; |
| 2671 | // Unknown reparse point | 2748 | // Unknown reparse point |
| 2672 | break :reparse_point .unknown; | 2749 | break :reparse_point .unknown; |
| ... | @@ -2853,7 +2930,6 @@ fn dirAccessWindows( | ... | @@ -2853,7 +2930,6 @@ fn dirAccessWindows( |
| 2853 | ) Dir.AccessError!void { | 2930 | ) Dir.AccessError!void { |
| 2854 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 2931 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2855 | _ = t; | 2932 | _ = t; |
| 2856 | try Thread.checkCancel(); | | |
| 2857 | | 2933 | |
| 2858 | _ = options; // TODO | 2934 | _ = options; // TODO |
| 2859 | | 2935 | |
| ... | @@ -2879,16 +2955,21 @@ fn dirAccessWindows( | ... | @@ -2879,16 +2955,21 @@ fn dirAccessWindows( |
| 2879 | .SecurityQualityOfService = null, | 2955 | .SecurityQualityOfService = null, |
| 2880 | }; | 2956 | }; |
| 2881 | var basic_info: windows.FILE.BASIC_INFORMATION = undefined; | 2957 | var basic_info: windows.FILE.BASIC_INFORMATION = undefined; |
| 2882 | switch (windows.ntdll.NtQueryAttributesFile(&attr, &basic_info)) { | 2958 | const syscall: Syscall = try .start(); |
| 2883 | .SUCCESS => return, | 2959 | while (true) switch (windows.ntdll.NtQueryAttributesFile(&attr, &basic_info)) { |
| 2884 | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, | 2960 | .SUCCESS => return syscall.finish(), |
| 2885 | .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, | 2961 | .CANCELLED => { |
| 2886 | .OBJECT_NAME_INVALID => |err| return windows.statusBug(err), | 2962 | try syscall.checkCancel(); |
| 2887 | .INVALID_PARAMETER => |err| return windows.statusBug(err), | 2963 | continue; |
| 2888 | .ACCESS_DENIED => return error.AccessDenied, | 2964 | }, |
| 2889 | .OBJECT_PATH_SYNTAX_BAD => |err| return windows.statusBug(err), | 2965 | .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 2890 | else => |rc| return windows.unexpectedStatus(rc), | 2966 | .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 2891 | } | 2967 | .OBJECT_NAME_INVALID => |err| return syscall.ntstatusBug(err), |
| | 2968 | .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), |
| | 2969 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| | 2970 | .OBJECT_PATH_SYNTAX_BAD => |err| return syscall.ntstatusBug(err), |
| | 2971 | else => |rc| return syscall.unexpectedNtstatus(rc), |
| | 2972 | }; |
| 2892 | } | 2973 | } |
| 2893 | | 2974 | |
| 2894 | const dirCreateFile = switch (native_os) { | 2975 | const dirCreateFile = switch (native_os) { |
| ... | @@ -3071,27 +3152,40 @@ fn dirCreateFileWindows( | ... | @@ -3071,27 +3152,40 @@ fn dirCreateFileWindows( |
| 3071 | const w = windows; | 3152 | const w = windows; |
| 3072 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 3153 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 3073 | _ = t; | 3154 | _ = t; |
| 3074 | try Thread.checkCancel(); | | |
| 3075 | | 3155 | |
| 3076 | const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, sub_path); | 3156 | const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, sub_path); |
| 3077 | const sub_path_w = sub_path_w_array.span(); | 3157 | const sub_path_w = sub_path_w_array.span(); |
| 3078 | | 3158 | |
| 3079 | const handle = try w.OpenFile(sub_path_w, .{ | 3159 | const handle = handle: { |
| 3080 | .dir = dir.handle, | 3160 | const syscall: Syscall = try .start(); |
| 3081 | .access_mask = .{ | 3161 | while (true) { |
| 3082 | .STANDARD = .{ .SYNCHRONIZE = true }, | 3162 | if (w.OpenFile(sub_path_w, .{ |
| 3083 | .GENERIC = .{ | 3163 | .dir = dir.handle, |
| 3084 | .WRITE = true, | 3164 | .access_mask = .{ |
| 3085 | .READ = flags.read, | 3165 | .STANDARD = .{ .SYNCHRONIZE = true }, |
| 3086 | }, | 3166 | .GENERIC = .{ |
| 3087 | }, | 3167 | .WRITE = true, |
| 3088 | .creation = if (flags.exclusive) | 3168 | .READ = flags.read, |
| 3089 | .CREATE | 3169 | }, |
| 3090 | else if (flags.truncate) | 3170 | }, |
| 3091 | .OVERWRITE_IF | 3171 | .creation = if (flags.exclusive) |
| 3092 | else | 3172 | .CREATE |
| 3093 | .OPEN_IF, | 3173 | else if (flags.truncate) |
| 3094 | }); | 3174 | .OVERWRITE_IF |
| | 3175 | else |
| | 3176 | .OPEN_IF, |
| | 3177 | })) |handle| { |
| | 3178 | syscall.finish(); |
| | 3179 | break :handle handle; |
| | 3180 | } else |err| switch (err) { |
| | 3181 | error.OperationCanceled => { |
| | 3182 | try syscall.checkCancel(); |
| | 3183 | continue; |
| | 3184 | }, |
| | 3185 | else => |e| return syscall.fail(e), |
| | 3186 | } |
| | 3187 | } |
| | 3188 | }; |
| 3095 | errdefer w.CloseHandle(handle); | 3189 | errdefer w.CloseHandle(handle); |
| 3096 | | 3190 | |
| 3097 | var io_status_block: w.IO_STATUS_BLOCK = undefined; | 3191 | var io_status_block: w.IO_STATUS_BLOCK = undefined; |
| ... | @@ -3100,7 +3194,8 @@ fn dirCreateFileWindows( | ... | @@ -3100,7 +3194,8 @@ fn dirCreateFileWindows( |
| 3100 | .shared => false, | 3194 | .shared => false, |
| 3101 | .exclusive => true, | 3195 | .exclusive => true, |
| 3102 | }; | 3196 | }; |
| 3103 | const status = w.ntdll.NtLockFile( | 3197 | const syscall: Syscall = try .start(); |
| | 3198 | while (true) switch (w.ntdll.NtLockFile( |
| 3104 | handle, | 3199 | handle, |
| 3105 | null, | 3200 | null, |
| 3106 | null, | 3201 | null, |
| ... | @@ -3111,16 +3206,16 @@ fn dirCreateFileWindows( | ... | @@ -3111,16 +3206,16 @@ fn dirCreateFileWindows( |
| 3111 | null, | 3206 | null, |
| 3112 | @intFromBool(flags.lock_nonblocking), | 3207 | @intFromBool(flags.lock_nonblocking), |
| 3113 | @intFromBool(exclusive), | 3208 | @intFromBool(exclusive), |
| 3114 | ); | 3209 | )) { |
| 3115 | switch (status) { | 3210 | .SUCCESS => { |
| 3116 | .SUCCESS => {}, | 3211 | syscall.finish(); |
| 3117 | .INSUFFICIENT_RESOURCES => return error.SystemResources, | 3212 | return .{ .handle = handle }; |
| 3118 | .LOCK_NOT_GRANTED => return error.WouldBlock, | 3213 | }, |
| 3119 | .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer | 3214 | .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources), |
| 3120 | else => return windows.unexpectedStatus(status), | 3215 | .LOCK_NOT_GRANTED => return syscall.fail(error.WouldBlock), |
| 3121 | } | 3216 | .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer |
| 3122 | | 3217 | else => |status| return syscall.unexpectedNtstatus(status), |
| 3123 | return .{ .handle = handle }; | 3218 | }; |
| 3124 | } | 3219 | } |
| 3125 | | 3220 | |
| 3126 | fn dirCreateFileWasi( | 3221 | fn dirCreateFileWasi( |
| ... | @@ -3399,14 +3494,14 @@ fn dirOpenFileWindows( | ... | @@ -3399,14 +3494,14 @@ fn dirOpenFileWindows( |
| 3399 | flags: File.OpenFlags, | 3494 | flags: File.OpenFlags, |
| 3400 | ) File.OpenError!File { | 3495 | ) File.OpenError!File { |
| 3401 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 3496 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| | 3497 | _ = t; |
| 3402 | const sub_path_w_array = try windows.sliceToPrefixedFileW(dir.handle, sub_path); | 3498 | const sub_path_w_array = try windows.sliceToPrefixedFileW(dir.handle, sub_path); |
| 3403 | const sub_path_w = sub_path_w_array.span(); | 3499 | const sub_path_w = sub_path_w_array.span(); |
| 3404 | const dir_handle = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle; | 3500 | const dir_handle = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle; |
| 3405 | return dirOpenFileWtf16(t, dir_handle, sub_path_w, flags); | 3501 | return dirOpenFileWtf16(dir_handle, sub_path_w, flags); |
| 3406 | } | 3502 | } |
| 3407 | | 3503 | |
| 3408 | pub fn dirOpenFileWtf16( | 3504 | pub fn dirOpenFileWtf16( |
| 3409 | t: *Threaded, | | |
| 3410 | dir_handle: ?windows.HANDLE, | 3505 | dir_handle: ?windows.HANDLE, |
| 3411 | sub_path_w: [:0]const u16, | 3506 | sub_path_w: [:0]const u16, |
| 3412 | flags: File.OpenFlags, | 3507 | flags: File.OpenFlags, |
| ... | @@ -3415,7 +3510,6 @@ pub fn dirOpenFileWtf16( | ... | @@ -3415,7 +3510,6 @@ pub fn dirOpenFileWtf16( |
| 3415 | if (!allow_directory and std.mem.eql(u16, sub_path_w, &.{'.'})) return error.IsDir; | 3510 | if (!allow_directory and std.mem.eql(u16, sub_path_w, &.{'.'})) return error.IsDir; |
| 3416 | if (!allow_directory and std.mem.eql(u16, sub_path_w, &.{ '.', '.' })) return error.IsDir; | 3511 | if (!allow_directory and std.mem.eql(u16, sub_path_w, &.{ '.', '.' })) return error.IsDir; |
| 3417 | const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong; | 3512 | const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong; |
| 3418 | _ = t; | | |
| 3419 | const w = windows; | 3513 | const w = windows; |
| 3420 | | 3514 | |
| 3421 | var nt_name: w.UNICODE_STRING = .{ | 3515 | var nt_name: w.UNICODE_STRING = .{ |
| ... | @@ -3437,11 +3531,10 @@ pub fn dirOpenFileWtf16( | ... | @@ -3437,11 +3531,10 @@ pub fn dirOpenFileWtf16( |
| 3437 | const max_attempts = 13; | 3531 | const max_attempts = 13; |
| 3438 | var attempt: u5 = 0; | 3532 | var attempt: u5 = 0; |
| 3439 | | 3533 | |
| | 3534 | var syscall: Syscall = try .start(); |
| 3440 | const handle = while (true) { | 3535 | const handle = while (true) { |
| 3441 | try Thread.checkCancel(); | | |
| 3442 | | | |
| 3443 | var result: w.HANDLE = undefined; | 3536 | var result: w.HANDLE = undefined; |
| 3444 | const rc = w.ntdll.NtCreateFile( | 3537 | switch (w.ntdll.NtCreateFile( |
| 3445 | &result, | 3538 | &result, |
| 3446 | .{ | 3539 | .{ |
| 3447 | .STANDARD = .{ .SYNCHRONIZE = true }, | 3540 | .STANDARD = .{ .SYNCHRONIZE = true }, |
| ... | @@ -3463,49 +3556,59 @@ pub fn dirOpenFileWtf16( | ... | @@ -3463,49 +3556,59 @@ pub fn dirOpenFileWtf16( |
| 3463 | }, | 3556 | }, |
| 3464 | null, | 3557 | null, |
| 3465 | 0, | 3558 | 0, |
| 3466 | ); | 3559 | )) { |
| 3467 | switch (rc) { | 3560 | .SUCCESS => { |
| 3468 | .SUCCESS => break result, | 3561 | syscall.finish(); |
| 3469 | .OBJECT_NAME_INVALID => return error.BadPathName, | 3562 | break result; |
| 3470 | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, | 3563 | }, |
| 3471 | .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, | 3564 | .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), |
| 3472 | .BAD_NETWORK_PATH => return error.NetworkNotFound, // \\server was not found | 3565 | .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 3473 | .BAD_NETWORK_NAME => return error.NetworkNotFound, // \\server was found but \\server\share wasn't | 3566 | .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 3474 | .NO_MEDIA_IN_DEVICE => return error.NoDevice, | 3567 | .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found |
| 3475 | .INVALID_PARAMETER => |err| return w.statusBug(err), | 3568 | .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't |
| | 3569 | .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice), |
| | 3570 | .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), |
| | 3571 | .CANCELLED => { |
| | 3572 | try syscall.checkCancel(); |
| | 3573 | continue; |
| | 3574 | }, |
| 3476 | .SHARING_VIOLATION => { | 3575 | .SHARING_VIOLATION => { |
| 3477 | // This occurs if the file attempting to be opened is a running | 3576 | // This occurs if the file attempting to be opened is a running |
| 3478 | // executable. However, there's a kernel bug: the error may be | 3577 | // executable. However, there's a kernel bug: the error may be |
| 3479 | // incorrectly returned for an indeterminate amount of time | 3578 | // incorrectly returned for an indeterminate amount of time |
| 3480 | // after an executable file is closed. Here we work around the | 3579 | // after an executable file is closed. Here we work around the |
| 3481 | // kernel bug with retry attempts. | 3580 | // kernel bug with retry attempts. |
| | 3581 | syscall.finish(); |
| 3482 | if (max_attempts - attempt == 0) return error.SharingViolation; | 3582 | if (max_attempts - attempt == 0) return error.SharingViolation; |
| 3483 | _ = w.kernel32.SleepEx((@as(u32, 1) << attempt) >> 1, w.TRUE); | 3583 | _ = w.kernel32.SleepEx((@as(u32, 1) << attempt) >> 1, w.TRUE); |
| 3484 | attempt += 1; | 3584 | attempt += 1; |
| | 3585 | syscall = try .start(); |
| 3485 | continue; | 3586 | continue; |
| 3486 | }, | 3587 | }, |
| 3487 | .ACCESS_DENIED => return error.AccessDenied, | 3588 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| 3488 | .PIPE_BUSY => return error.PipeBusy, | 3589 | .PIPE_BUSY => return syscall.fail(error.PipeBusy), |
| 3489 | .PIPE_NOT_AVAILABLE => return error.NoDevice, | 3590 | .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice), |
| 3490 | .OBJECT_PATH_SYNTAX_BAD => |err| return w.statusBug(err), | 3591 | .OBJECT_PATH_SYNTAX_BAD => |err| return syscall.ntstatusBug(err), |
| 3491 | .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, | 3592 | .OBJECT_NAME_COLLISION => return syscall.fail(error.PathAlreadyExists), |
| 3492 | .FILE_IS_A_DIRECTORY => return error.IsDir, | 3593 | .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir), |
| 3493 | .NOT_A_DIRECTORY => return error.NotDir, | 3594 | .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), |
| 3494 | .USER_MAPPED_FILE => return error.AccessDenied, | 3595 | .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied), |
| 3495 | .INVALID_HANDLE => |err| return w.statusBug(err), | 3596 | .INVALID_HANDLE => |err| return syscall.ntstatusBug(err), |
| 3496 | .DELETE_PENDING => { | 3597 | .DELETE_PENDING => { |
| 3497 | // This error means that there *was* a file in this location on | 3598 | // This error means that there *was* a file in this location on |
| 3498 | // the file system, but it was deleted. However, the OS is not | 3599 | // the file system, but it was deleted. However, the OS is not |
| 3499 | // finished with the deletion operation, and so this CreateFile | 3600 | // finished with the deletion operation, and so this CreateFile |
| 3500 | // call has failed. Here, we simulate the kernel bug being | 3601 | // call has failed. Here, we simulate the kernel bug being |
| 3501 | // fixed by sleeping and retrying until the error goes away. | 3602 | // fixed by sleeping and retrying until the error goes away. |
| | 3603 | syscall.finish(); |
| 3502 | if (max_attempts - attempt == 0) return error.SharingViolation; | 3604 | if (max_attempts - attempt == 0) return error.SharingViolation; |
| 3503 | _ = w.kernel32.SleepEx((@as(u32, 1) << attempt) >> 1, w.TRUE); | 3605 | _ = w.kernel32.SleepEx((@as(u32, 1) << attempt) >> 1, w.TRUE); |
| 3504 | attempt += 1; | 3606 | attempt += 1; |
| | 3607 | syscall = try .start(); |
| 3505 | continue; | 3608 | continue; |
| 3506 | }, | 3609 | }, |
| 3507 | .VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference, | 3610 | .VIRUS_INFECTED, .VIRUS_DELETED => return syscall.fail(error.AntivirusInterference), |
| 3508 | else => return w.unexpectedStatus(rc), | 3611 | else => |rc| return syscall.unexpectedNtstatus(rc), |
| 3509 | } | 3612 | } |
| 3510 | }; | 3613 | }; |
| 3511 | errdefer w.CloseHandle(handle); | 3614 | errdefer w.CloseHandle(handle); |
| ... | @@ -3515,7 +3618,8 @@ pub fn dirOpenFileWtf16( | ... | @@ -3515,7 +3618,8 @@ pub fn dirOpenFileWtf16( |
| 3515 | .shared => false, | 3618 | .shared => false, |
| 3516 | .exclusive => true, | 3619 | .exclusive => true, |
| 3517 | }; | 3620 | }; |
| 3518 | const status = w.ntdll.NtLockFile( | 3621 | syscall = try .start(); |
| | 3622 | while (true) switch (w.ntdll.NtLockFile( |
| 3519 | handle, | 3623 | handle, |
| 3520 | null, | 3624 | null, |
| 3521 | null, | 3625 | null, |
| ... | @@ -3526,14 +3630,13 @@ pub fn dirOpenFileWtf16( | ... | @@ -3526,14 +3630,13 @@ pub fn dirOpenFileWtf16( |
| 3526 | null, | 3630 | null, |
| 3527 | @intFromBool(flags.lock_nonblocking), | 3631 | @intFromBool(flags.lock_nonblocking), |
| 3528 | @intFromBool(exclusive), | 3632 | @intFromBool(exclusive), |
| 3529 | ); | 3633 | )) { |
| 3530 | switch (status) { | 3634 | .SUCCESS => break syscall.finish(), |
| 3531 | .SUCCESS => {}, | 3635 | .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources), |
| 3532 | .INSUFFICIENT_RESOURCES => return error.SystemResources, | 3636 | .LOCK_NOT_GRANTED => return syscall.fail(error.WouldBlock), |
| 3533 | .LOCK_NOT_GRANTED => return error.WouldBlock, | 3637 | .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer |
| 3534 | .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer | 3638 | else => |status| return syscall.unexpectedNtstatus(status), |
| 3535 | else => return windows.unexpectedStatus(status), | 3639 | }; |
| 3536 | } | | |
| 3537 | return .{ .handle = handle }; | 3640 | return .{ .handle = handle }; |
| 3538 | } | 3641 | } |
| 3539 | | 3642 | |
| ... | @@ -3773,8 +3876,9 @@ pub fn dirOpenDirWindows( | ... | @@ -3773,8 +3876,9 @@ pub fn dirOpenDirWindows( |
| 3773 | }; | 3876 | }; |
| 3774 | var io_status_block: w.IO_STATUS_BLOCK = undefined; | 3877 | var io_status_block: w.IO_STATUS_BLOCK = undefined; |
| 3775 | var result: Dir = .{ .handle = undefined }; | 3878 | var result: Dir = .{ .handle = undefined }; |
| 3776 | try Thread.checkCancel(); | 3879 | |
| 3777 | const rc = w.ntdll.NtCreateFile( | 3880 | const syscall: Syscall = try .start(); |
| | 3881 | while (true) switch (w.ntdll.NtCreateFile( |
| 3778 | &result.handle, | 3882 | &result.handle, |
| 3779 | // TODO remove some of these flags if options.access_sub_paths is false | 3883 | // TODO remove some of these flags if options.access_sub_paths is false |
| 3780 | .{ | 3884 | .{ |
| ... | @@ -3810,21 +3914,26 @@ pub fn dirOpenDirWindows( | ... | @@ -3810,21 +3914,26 @@ pub fn dirOpenDirWindows( |
| 3810 | }, | 3914 | }, |
| 3811 | null, | 3915 | null, |
| 3812 | 0, | 3916 | 0, |
| 3813 | ); | 3917 | )) { |
| 3814 | | 3918 | .SUCCESS => { |
| 3815 | switch (rc) { | 3919 | syscall.finish(); |
| 3816 | .SUCCESS => return result, | 3920 | return result; |
| 3817 | .OBJECT_NAME_INVALID => return error.BadPathName, | 3921 | }, |
| 3818 | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, | 3922 | .CANCELLED => { |
| | 3923 | try syscall.checkCancel(); |
| | 3924 | continue; |
| | 3925 | }, |
| | 3926 | .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), |
| | 3927 | .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 3819 | .OBJECT_NAME_COLLISION => |err| return w.statusBug(err), | 3928 | .OBJECT_NAME_COLLISION => |err| return w.statusBug(err), |
| 3820 | .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, | 3929 | .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 3821 | .NOT_A_DIRECTORY => return error.NotDir, | 3930 | .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), |
| 3822 | // This can happen if the directory has 'List folder contents' permission set to 'Deny' | 3931 | // This can happen if the directory has 'List folder contents' permission set to 'Deny' |
| 3823 | // and the directory is trying to be opened for iteration. | 3932 | // and the directory is trying to be opened for iteration. |
| 3824 | .ACCESS_DENIED => return error.AccessDenied, | 3933 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| 3825 | .INVALID_PARAMETER => |err| return w.statusBug(err), | 3934 | .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), |
| 3826 | else => return w.unexpectedStatus(rc), | 3935 | else => |rc| return syscall.unexpectedNtstatus(rc), |
| 3827 | } | 3936 | }; |
| 3828 | } | 3937 | } |
| 3829 | | 3938 | |
| 3830 | fn dirClose(userdata: ?*anyopaque, dirs: []const Dir) void { | 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,9 +4373,9 @@ fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) D |
| 4264 | // buffered data. | 4373 | // buffered data. |
| 4265 | if (buffer_index != 0) break; | 4374 | if (buffer_index != 0) break; |
| 4266 | | 4375 | |
| 4267 | try Thread.checkCancel(); | | |
| 4268 | var io_status_block: w.IO_STATUS_BLOCK = undefined; | 4376 | var io_status_block: w.IO_STATUS_BLOCK = undefined; |
| 4269 | const rc = w.ntdll.NtQueryDirectoryFile( | 4377 | const syscall: Syscall = try .start(); |
| | 4378 | const rc = while (true) switch (w.ntdll.NtQueryDirectoryFile( |
| 4270 | dr.dir.handle, | 4379 | dr.dir.handle, |
| 4271 | null, | 4380 | null, |
| 4272 | null, | 4381 | null, |
| ... | @@ -4278,7 +4387,16 @@ fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) D | ... | @@ -4278,7 +4387,16 @@ fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) D |
| 4278 | w.FALSE, | 4387 | w.FALSE, |
| 4279 | null, | 4388 | null, |
| 4280 | @intFromBool(dr.state == .reset), | 4389 | @intFromBool(dr.state == .reset), |
| 4281 | ); | 4390 | )) { |
| | 4391 | .CANCELLED => { |
| | 4392 | try syscall.checkCancel(); |
| | 4393 | continue; |
| | 4394 | }, |
| | 4395 | else => |rc| { |
| | 4396 | syscall.finish(); |
| | 4397 | break rc; |
| | 4398 | }, |
| | 4399 | }; |
| 4282 | dr.state = .reading; | 4400 | dr.state = .reading; |
| 4283 | if (io_status_block.Information == 0) { | 4401 | if (io_status_block.Information == 0) { |
| 4284 | dr.state = .finished; | 4402 | dr.state = .finished; |
| ... | @@ -4466,32 +4584,40 @@ fn dirRealPathFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, | ... | @@ -4466,32 +4584,40 @@ fn dirRealPathFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, |
| 4466 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 4584 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 4467 | _ = t; | 4585 | _ = t; |
| 4468 | | 4586 | |
| 4469 | try Thread.checkCancel(); | | |
| 4470 | | | |
| 4471 | var path_name_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path); | 4587 | var path_name_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path); |
| 4472 | | 4588 | |
| 4473 | const h_file = blk: { | 4589 | const h_file = handle: { |
| 4474 | const res = windows.OpenFile(path_name_w.span(), .{ | 4590 | const syscall: Syscall = try .start(); |
| 4475 | .dir = dir.handle, | 4591 | while (true) { |
| 4476 | .access_mask = .{ | 4592 | if (windows.OpenFile(path_name_w.span(), .{ |
| 4477 | .GENERIC = .{ .READ = true }, | 4593 | .dir = dir.handle, |
| 4478 | .STANDARD = .{ .SYNCHRONIZE = true }, | 4594 | .access_mask = .{ |
| 4479 | }, | 4595 | .GENERIC = .{ .READ = true }, |
| 4480 | .creation = .OPEN, | 4596 | .STANDARD = .{ .SYNCHRONIZE = true }, |
| 4481 | .filter = .any, | 4597 | }, |
| 4482 | }) catch |err| switch (err) { | 4598 | .creation = .OPEN, |
| 4483 | error.WouldBlock => unreachable, | 4599 | .filter = .any, |
| 4484 | else => |e| return e, | 4600 | })) |handle| { |
| 4485 | }; | 4601 | syscall.finish(); |
| 4486 | break :blk res; | 4602 | break :handle handle; |
| | 4603 | } else |err| switch (err) { |
| | 4604 | error.WouldBlock => unreachable, |
| | 4605 | error.OperationCanceled => { |
| | 4606 | try syscall.checkCancel(); |
| | 4607 | continue; |
| | 4608 | }, |
| | 4609 | else => |e| return syscall.fail(e), |
| | 4610 | } |
| | 4611 | } |
| 4487 | }; | 4612 | }; |
| 4488 | defer windows.CloseHandle(h_file); | 4613 | defer windows.CloseHandle(h_file); |
| 4489 | return realPathWindows(h_file, out_buffer); | 4614 | return realPathWindows(h_file, out_buffer); |
| 4490 | } | 4615 | } |
| 4491 | | 4616 | |
| 4492 | fn realPathWindows(h_file: windows.HANDLE, out_buffer: []u8) File.RealPathError!usize { | 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 | var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; | 4618 | var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; |
| | 4619 | // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks |
| | 4620 | try Thread.checkCancel(); |
| 4495 | const wide_slice = try windows.GetFinalPathNameByHandle(h_file, .{}, &wide_buf); | 4621 | const wide_slice = try windows.GetFinalPathNameByHandle(h_file, .{}, &wide_buf); |
| 4496 | | 4622 | |
| 4497 | const len = std.unicode.calcWtf8Len(wide_slice); | 4623 | const len = std.unicode.calcWtf8Len(wide_slice); |
| ... | @@ -4885,8 +5011,6 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov | ... | @@ -4885,8 +5011,6 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov |
| 4885 | _ = t; | 5011 | _ = t; |
| 4886 | const w = windows; | 5012 | const w = windows; |
| 4887 | | 5013 | |
| 4888 | try Thread.checkCancel(); | | |
| 4889 | | | |
| 4890 | const sub_path_w_buf = try w.sliceToPrefixedFileW(dir.handle, sub_path); | 5014 | const sub_path_w_buf = try w.sliceToPrefixedFileW(dir.handle, sub_path); |
| 4891 | const sub_path_w = sub_path_w_buf.span(); | 5015 | const sub_path_w = sub_path_w_buf.span(); |
| 4892 | | 5016 | |
| ... | @@ -4909,47 +5033,49 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov | ... | @@ -4909,47 +5033,49 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov |
| 4909 | | 5033 | |
| 4910 | var io_status_block: w.IO_STATUS_BLOCK = undefined; | 5034 | var io_status_block: w.IO_STATUS_BLOCK = undefined; |
| 4911 | var tmp_handle: w.HANDLE = undefined; | 5035 | var tmp_handle: w.HANDLE = undefined; |
| 4912 | var rc = w.ntdll.NtCreateFile( | 5036 | { |
| 4913 | &tmp_handle, | 5037 | const syscall: Syscall = try .start(); |
| 4914 | .{ .STANDARD = .{ | 5038 | while (true) switch (w.ntdll.NtCreateFile( |
| 4915 | .RIGHTS = .{ .DELETE = true }, | 5039 | &tmp_handle, |
| 4916 | .SYNCHRONIZE = true, | 5040 | .{ .STANDARD = .{ |
| 4917 | } }, | 5041 | .RIGHTS = .{ .DELETE = true }, |
| 4918 | &.{ | 5042 | .SYNCHRONIZE = true, |
| 4919 | .Length = @sizeOf(w.OBJECT_ATTRIBUTES), | 5043 | } }, |
| 4920 | .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, | 5044 | &.{ |
| 4921 | .Attributes = .{}, | 5045 | .Length = @sizeOf(w.OBJECT_ATTRIBUTES), |
| 4922 | .ObjectName = &nt_name, | 5046 | .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, |
| 4923 | .SecurityDescriptor = null, | 5047 | .Attributes = .{}, |
| 4924 | .SecurityQualityOfService = null, | 5048 | .ObjectName = &nt_name, |
| 4925 | }, | 5049 | .SecurityDescriptor = null, |
| 4926 | &io_status_block, | 5050 | .SecurityQualityOfService = null, |
| 4927 | null, | 5051 | }, |
| 4928 | .{}, | 5052 | &io_status_block, |
| 4929 | .VALID_FLAGS, | 5053 | null, |
| 4930 | .OPEN, | 5054 | .{}, |
| 4931 | .{ | 5055 | .VALID_FLAGS, |
| 4932 | .DIRECTORY_FILE = remove_dir, | 5056 | .OPEN, |
| 4933 | .NON_DIRECTORY_FILE = !remove_dir, | 5057 | .{ |
| 4934 | .OPEN_REPARSE_POINT = true, // would we ever want to delete the target instead? | 5058 | .DIRECTORY_FILE = remove_dir, |
| 4935 | }, | 5059 | .NON_DIRECTORY_FILE = !remove_dir, |
| 4936 | null, | 5060 | .OPEN_REPARSE_POINT = true, // would we ever want to delete the target instead? |
| 4937 | 0, | 5061 | }, |
| 4938 | ); | 5062 | null, |
| 4939 | switch (rc) { | 5063 | 0, |
| 4940 | .SUCCESS => {}, | 5064 | )) { |
| 4941 | .OBJECT_NAME_INVALID => |err| return w.statusBug(err), | 5065 | .SUCCESS => break syscall.finish(), |
| 4942 | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, | 5066 | .OBJECT_NAME_INVALID => |err| return syscall.ntstatusBug(err), |
| 4943 | .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, | 5067 | .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 4944 | .BAD_NETWORK_PATH => return error.NetworkNotFound, // \\server was not found | 5068 | .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 4945 | .BAD_NETWORK_NAME => return error.NetworkNotFound, // \\server was found but \\server\share wasn't | 5069 | .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found |
| 4946 | .INVALID_PARAMETER => |err| return w.statusBug(err), | 5070 | .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't |
| 4947 | .FILE_IS_A_DIRECTORY => return error.IsDir, | 5071 | .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), |
| 4948 | .NOT_A_DIRECTORY => return error.NotDir, | 5072 | .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir), |
| 4949 | .SHARING_VIOLATION => return error.FileBusy, | 5073 | .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), |
| 4950 | .ACCESS_DENIED => return error.AccessDenied, | 5074 | .SHARING_VIOLATION => return syscall.fail(error.FileBusy), |
| 4951 | .DELETE_PENDING => return, | 5075 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| 4952 | else => return w.unexpectedStatus(rc), | 5076 | .DELETE_PENDING => return syscall.finish(), |
| | 5077 | else => |rc| return syscall.unexpectedNtstatus(rc), |
| | 5078 | }; |
| 4953 | } | 5079 | } |
| 4954 | defer w.CloseHandle(tmp_handle); | 5080 | defer w.CloseHandle(tmp_handle); |
| 4955 | | 5081 | |
| ... | @@ -4964,9 +5090,7 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov | ... | @@ -4964,9 +5090,7 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov |
| 4964 | // | 5090 | // |
| 4965 | // The strategy here is just to try using FileDispositionInformationEx and fall back to | 5091 | // The strategy here is just to try using FileDispositionInformationEx and fall back to |
| 4966 | // FileDispositionInformation if the return value lets us know that some aspect of it is not supported. | 5092 | // FileDispositionInformation if the return value lets us know that some aspect of it is not supported. |
| 4967 | const need_fallback = need_fallback: { | 5093 | const rc = rc: { |
| 4968 | try Thread.checkCancel(); | | |
| 4969 | | | |
| 4970 | // Deletion with posix semantics if the filesystem supports it. | 5094 | // Deletion with posix semantics if the filesystem supports it. |
| 4971 | const info: w.FILE.DISPOSITION.INFORMATION.EX = .{ .Flags = .{ | 5095 | const info: w.FILE.DISPOSITION.INFORMATION.EX = .{ .Flags = .{ |
| 4972 | .DELETE = true, | 5096 | .DELETE = true, |
| ... | @@ -4974,29 +5098,32 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov | ... | @@ -4974,29 +5098,32 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov |
| 4974 | .IGNORE_READONLY_ATTRIBUTE = true, | 5098 | .IGNORE_READONLY_ATTRIBUTE = true, |
| 4975 | } }; | 5099 | } }; |
| 4976 | | 5100 | |
| 4977 | rc = w.ntdll.NtSetInformationFile( | 5101 | const syscall: Syscall = try .start(); |
| | 5102 | while (true) switch (w.ntdll.NtSetInformationFile( |
| 4978 | tmp_handle, | 5103 | tmp_handle, |
| 4979 | &io_status_block, | 5104 | &io_status_block, |
| 4980 | &info, | 5105 | &info, |
| 4981 | @sizeOf(w.FILE.DISPOSITION.INFORMATION.EX), | 5106 | @sizeOf(w.FILE.DISPOSITION.INFORMATION.EX), |
| 4982 | .DispositionEx, | 5107 | .DispositionEx, |
| 4983 | ); | 5108 | )) { |
| 4984 | switch (rc) { | 5109 | .CANCELLED => { |
| 4985 | .SUCCESS => return, | 5110 | try syscall.checkCancel(); |
| | 5111 | continue; |
| | 5112 | }, |
| 4986 | // The filesystem does not support FileDispositionInformationEx | 5113 | // The filesystem does not support FileDispositionInformationEx |
| 4987 | .INVALID_PARAMETER, | 5114 | .INVALID_PARAMETER, |
| 4988 | // The operating system does not support FileDispositionInformationEx | 5115 | // The operating system does not support FileDispositionInformationEx |
| 4989 | .INVALID_INFO_CLASS, | 5116 | .INVALID_INFO_CLASS, |
| 4990 | // The operating system does not support one of the flags | 5117 | // The operating system does not support one of the flags |
| 4991 | .NOT_SUPPORTED, | 5118 | .NOT_SUPPORTED, |
| 4992 | => break :need_fallback true, | 5119 | => break, // use fallback path below; `syscall` still active |
| 4993 | // For all other statuses, fall down to the switch below to handle them. | | |
| 4994 | else => break :need_fallback false, | | |
| 4995 | } | | |
| 4996 | }; | | |
| 4997 | | 5120 | |
| 4998 | if (need_fallback) { | 5121 | // For all other statuses, fall down to the switch below to handle them. |
| 4999 | try Thread.checkCancel(); | 5122 | else => |rc| { |
| | 5123 | syscall.finish(); |
| | 5124 | break :rc rc; |
| | 5125 | }, |
| | 5126 | }; |
| 5000 | | 5127 | |
| 5001 | // Deletion with file pending semantics, which requires waiting or moving | 5128 | // Deletion with file pending semantics, which requires waiting or moving |
| 5002 | // files to get them removed (from here). | 5129 | // files to get them removed (from here). |
| ... | @@ -5004,14 +5131,23 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov | ... | @@ -5004,14 +5131,23 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov |
| 5004 | .DeleteFile = w.TRUE, | 5131 | .DeleteFile = w.TRUE, |
| 5005 | }; | 5132 | }; |
| 5006 | | 5133 | |
| 5007 | rc = w.ntdll.NtSetInformationFile( | 5134 | while (true) switch (w.ntdll.NtSetInformationFile( |
| 5008 | tmp_handle, | 5135 | tmp_handle, |
| 5009 | &io_status_block, | 5136 | &io_status_block, |
| 5010 | &file_dispo, | 5137 | &file_dispo, |
| 5011 | @sizeOf(w.FILE.DISPOSITION.INFORMATION), | 5138 | @sizeOf(w.FILE.DISPOSITION.INFORMATION), |
| 5012 | .Disposition, | 5139 | .Disposition, |
| 5013 | ); | 5140 | )) { |
| 5014 | } | 5141 | .CANCELLED => { |
| | 5142 | try syscall.checkCancel(); |
| | 5143 | continue; |
| | 5144 | }, |
| | 5145 | else => |rc| { |
| | 5146 | syscall.finish(); |
| | 5147 | break :rc rc; |
| | 5148 | }, |
| | 5149 | }; |
| | 5150 | }; |
| 5015 | switch (rc) { | 5151 | switch (rc) { |
| 5016 | .SUCCESS => {}, | 5152 | .SUCCESS => {}, |
| 5017 | .DIRECTORY_NOT_EMPTY => return error.DirNotEmpty, | 5153 | .DIRECTORY_NOT_EMPTY => return error.DirNotEmpty, |
| ... | @@ -5135,23 +5271,33 @@ fn dirRenameWindows( | ... | @@ -5135,23 +5271,33 @@ fn dirRenameWindows( |
| 5135 | const new_path_w = new_path_w_buf.span(); | 5271 | const new_path_w = new_path_w_buf.span(); |
| 5136 | const replace_if_exists = true; | 5272 | const replace_if_exists = true; |
| 5137 | | 5273 | |
| 5138 | try Thread.checkCancel(); | 5274 | const src_fd = src_fd: { |
| 5139 | | 5275 | const syscall: Syscall = try .start(); |
| 5140 | const src_fd = w.OpenFile(old_path_w, .{ | 5276 | while (true) { |
| 5141 | .dir = old_dir.handle, | 5277 | if (w.OpenFile(old_path_w, .{ |
| 5142 | .access_mask = .{ | 5278 | .dir = old_dir.handle, |
| 5143 | .GENERIC = .{ .WRITE = true }, | 5279 | .access_mask = .{ |
| 5144 | .STANDARD = .{ | 5280 | .GENERIC = .{ .WRITE = true }, |
| 5145 | .RIGHTS = .{ .DELETE = true }, | 5281 | .STANDARD = .{ |
| 5146 | .SYNCHRONIZE = true, | 5282 | .RIGHTS = .{ .DELETE = true }, |
| 5147 | }, | 5283 | .SYNCHRONIZE = true, |
| 5148 | }, | 5284 | }, |
| 5149 | .creation = .OPEN, | 5285 | }, |
| 5150 | .filter = .any, // This function is supposed to rename both files and directories. | 5286 | .creation = .OPEN, |
| 5151 | .follow_symlinks = false, | 5287 | .filter = .any, // This function is supposed to rename both files and directories. |
| 5152 | }) catch |err| switch (err) { | 5288 | .follow_symlinks = false, |
| 5153 | error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`. | 5289 | })) |handle| { |
| 5154 | else => |e| return e, | 5290 | syscall.finish(); |
| | 5291 | break :src_fd handle; |
| | 5292 | } else |err| switch (err) { |
| | 5293 | error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`. |
| | 5294 | error.OperationCanceled => { |
| | 5295 | try syscall.checkCancel(); |
| | 5296 | continue; |
| | 5297 | }, |
| | 5298 | else => |e| return e, |
| | 5299 | } |
| | 5300 | } |
| 5155 | }; | 5301 | }; |
| 5156 | defer w.CloseHandle(src_fd); | 5302 | defer w.CloseHandle(src_fd); |
| 5157 | | 5303 | |
| ... | @@ -5354,8 +5500,6 @@ fn dirSymLinkWindows( | ... | @@ -5354,8 +5500,6 @@ fn dirSymLinkWindows( |
| 5354 | _ = t; | 5500 | _ = t; |
| 5355 | const w = windows; | 5501 | const w = windows; |
| 5356 | | 5502 | |
| 5357 | try Thread.checkCancel(); | | |
| 5358 | | | |
| 5359 | // Target path does not use sliceToPrefixedFileW because certain paths | 5503 | // Target path does not use sliceToPrefixedFileW because certain paths |
| 5360 | // are handled differently when creating a symlink than they would be | 5504 | // are handled differently when creating a symlink than they would be |
| 5361 | // when converting to an NT namespaced path. CreateSymbolicLink in | 5505 | // when converting to an NT namespaced path. CreateSymbolicLink in |
| ... | @@ -5385,22 +5529,34 @@ fn dirSymLinkWindows( | ... | @@ -5385,22 +5529,34 @@ fn dirSymLinkWindows( |
| 5385 | Flags: w.ULONG, | 5529 | Flags: w.ULONG, |
| 5386 | }; | 5530 | }; |
| 5387 | | 5531 | |
| 5388 | const symlink_handle = w.OpenFile(sym_link_path_w.span(), .{ | 5532 | const symlink_handle = handle: { |
| 5389 | .access_mask = .{ | 5533 | const syscall: Syscall = try .start(); |
| 5390 | .GENERIC = .{ .READ = true, .WRITE = true }, | 5534 | while (true) { |
| 5391 | .STANDARD = .{ .SYNCHRONIZE = true }, | 5535 | if (w.OpenFile(sym_link_path_w.span(), .{ |
| 5392 | }, | 5536 | .access_mask = .{ |
| 5393 | .dir = dir.handle, | 5537 | .GENERIC = .{ .READ = true, .WRITE = true }, |
| 5394 | .creation = .CREATE, | 5538 | .STANDARD = .{ .SYNCHRONIZE = true }, |
| 5395 | .filter = if (flags.is_directory) .dir_only else .non_directory_only, | 5539 | }, |
| 5396 | }) catch |err| switch (err) { | 5540 | .dir = dir.handle, |
| 5397 | error.IsDir => return error.PathAlreadyExists, | 5541 | .creation = .CREATE, |
| 5398 | error.NotDir => return error.Unexpected, | 5542 | .filter = if (flags.is_directory) .dir_only else .non_directory_only, |
| 5399 | error.WouldBlock => return error.Unexpected, | 5543 | })) |handle| { |
| 5400 | error.PipeBusy => return error.Unexpected, | 5544 | syscall.finish(); |
| 5401 | error.NoDevice => return error.Unexpected, | 5545 | break :handle handle; |
| 5402 | error.AntivirusInterference => return error.Unexpected, | 5546 | } else |err| switch (err) { |
| 5403 | else => |e| return e, | 5547 | error.IsDir => return syscall.fail(error.PathAlreadyExists), |
| | 5548 | error.NotDir => return syscall.fail(error.Unexpected), |
| | 5549 | error.WouldBlock => return syscall.fail(error.Unexpected), |
| | 5550 | error.PipeBusy => return syscall.fail(error.Unexpected), |
| | 5551 | error.NoDevice => return syscall.fail(error.Unexpected), |
| | 5552 | error.AntivirusInterference => return syscall.fail(error.Unexpected), |
| | 5553 | error.OperationCanceled => { |
| | 5554 | try syscall.checkCancel(); |
| | 5555 | continue; |
| | 5556 | }, |
| | 5557 | else => |e| return e, |
| | 5558 | } |
| | 5559 | } |
| 5404 | }; | 5560 | }; |
| 5405 | defer w.CloseHandle(symlink_handle); | 5561 | defer w.CloseHandle(symlink_handle); |
| 5406 | | 5562 | |
| ... | @@ -5576,11 +5732,21 @@ fn dirReadLinkWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buf | ... | @@ -5576,11 +5732,21 @@ fn dirReadLinkWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buf |
| 5576 | _ = t; | 5732 | _ = t; |
| 5577 | const w = windows; | 5733 | const w = windows; |
| 5578 | | 5734 | |
| 5579 | try Thread.checkCancel(); | | |
| 5580 | | | |
| 5581 | var sub_path_w_buf = try windows.sliceToPrefixedFileW(dir.handle, sub_path); | 5735 | var sub_path_w_buf = try windows.sliceToPrefixedFileW(dir.handle, sub_path); |
| 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 | const len = std.unicode.calcWtf8Len(result_w); | 5751 | const len = std.unicode.calcWtf8Len(result_w); |
| 5586 | if (len > buffer.len) return error.NameTooLong; | 5752 | if (len > buffer.len) return error.NameTooLong; |
| ... | @@ -5997,17 +6163,25 @@ fn fileSyncWindows(userdata: ?*anyopaque, file: File) File.SyncError!void { | ... | @@ -5997,17 +6163,25 @@ fn fileSyncWindows(userdata: ?*anyopaque, file: File) File.SyncError!void { |
| 5997 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 6163 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 5998 | _ = t; | 6164 | _ = t; |
| 5999 | | 6165 | |
| 6000 | try Thread.checkCancel(); | 6166 | const syscall: Syscall = try .start(); |
| 6001 | | 6167 | while (true) { |
| 6002 | if (windows.kernel32.FlushFileBuffers(file.handle) != 0) | 6168 | if (windows.kernel32.FlushFileBuffers(file.handle) != 0) { |
| 6003 | return; | 6169 | return syscall.finish(); |
| 6004 | | 6170 | } |
| 6005 | switch (windows.GetLastError()) { | 6171 | switch (windows.GetLastError()) { |
| 6006 | .SUCCESS => return, | 6172 | .SUCCESS => unreachable, // `FlushFileBuffers` returned nonzero |
| 6007 | .INVALID_HANDLE => unreachable, | 6173 | .INVALID_HANDLE => unreachable, |
| 6008 | .ACCESS_DENIED => return error.AccessDenied, // a sync was performed but the system couldn't update the access time | 6174 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), // a sync was performed but the system couldn't update the access time |
| 6009 | .UNEXP_NET_ERR => return error.InputOutput, | 6175 | .UNEXP_NET_ERR => return syscall.fail(error.InputOutput), |
| 6010 | else => |err| return windows.unexpectedError(err), | 6176 | .OPERATION_ABORTED => { |
| | 6177 | try syscall.checkCancel(); |
| | 6178 | continue; |
| | 6179 | }, |
| | 6180 | else => |err| { |
| | 6181 | syscall.finish(); |
| | 6182 | return windows.unexpectedError(err); |
| | 6183 | }, |
| | 6184 | } |
| 6011 | } | 6185 | } |
| 6012 | } | 6186 | } |
| 6013 | | 6187 | |
| ... | @@ -6074,9 +6248,22 @@ fn fileIsTty(userdata: ?*anyopaque, file: File) Io.Cancelable!bool { | ... | @@ -6074,9 +6248,22 @@ fn fileIsTty(userdata: ?*anyopaque, file: File) Io.Cancelable!bool { |
| 6074 | fn isTty(file: File) Io.Cancelable!bool { | 6248 | fn isTty(file: File) Io.Cancelable!bool { |
| 6075 | if (is_windows) { | 6249 | if (is_windows) { |
| 6076 | if (try isCygwinPty(file)) return true; | 6250 | if (try isCygwinPty(file)) return true; |
| 6077 | try Thread.checkCancel(); | | |
| 6078 | var out: windows.DWORD = undefined; | 6251 | var out: windows.DWORD = undefined; |
| 6079 | return windows.kernel32.GetConsoleMode(file.handle, &out) != 0; | 6252 | const syscall: Syscall = try .start(); |
| | 6253 | while (windows.kernel32.GetConsoleMode(file.handle, &out) == 0) { |
| | 6254 | switch (windows.GetLastError()) { |
| | 6255 | .OPERATION_ABORTED => { |
| | 6256 | try syscall.checkCancel(); |
| | 6257 | continue; |
| | 6258 | }, |
| | 6259 | else => { |
| | 6260 | syscall.finish(); |
| | 6261 | return false; |
| | 6262 | }, |
| | 6263 | } |
| | 6264 | } |
| | 6265 | syscall.finish(); |
| | 6266 | return true; |
| 6080 | } | 6267 | } |
| 6081 | | 6268 | |
| 6082 | if (builtin.link_libc) { | 6269 | if (builtin.link_libc) { |
| ... | @@ -6146,35 +6333,65 @@ fn fileEnableAnsiEscapeCodes(userdata: ?*anyopaque, file: File) File.EnableAnsiE | ... | @@ -6146,35 +6333,65 @@ fn fileEnableAnsiEscapeCodes(userdata: ?*anyopaque, file: File) File.EnableAnsiE |
| 6146 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 6333 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 6147 | _ = t; | 6334 | _ = t; |
| 6148 | | 6335 | |
| 6149 | if (is_windows) { | 6336 | if (!is_windows) { |
| 6150 | try Thread.checkCancel(); | 6337 | if (try supportsAnsiEscapeCodes(file)) return; |
| | 6338 | return error.NotTerminalDevice; |
| | 6339 | } |
| 6151 | | 6340 | |
| 6152 | // For Windows Terminal, VT Sequences processing is enabled by default. | 6341 | // For Windows Terminal, VT Sequences processing is enabled by default. |
| 6153 | var original_console_mode: windows.DWORD = 0; | 6342 | var original_console_mode: windows.DWORD = 0; |
| 6154 | if (windows.kernel32.GetConsoleMode(file.handle, &original_console_mode) != 0) { | | |
| 6155 | if (original_console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return; | | |
| 6156 | | 6343 | |
| 6157 | // For Windows Console, VT Sequences processing support was added in Windows 10 build 14361, but disabled by default. | 6344 | { |
| 6158 | // https://devblogs.microsoft.com/commandline/tmux-support-arrives-for-bash-on-ubuntu-on-windows/ | 6345 | const syscall: Syscall = try .start(); |
| 6159 | // | 6346 | while (windows.kernel32.GetConsoleMode(file.handle, &original_console_mode) == 0) { |
| 6160 | // Note: In Microsoft's example for enabling virtual terminal processing, it | 6347 | switch (windows.GetLastError()) { |
| 6161 | // shows attempting to enable `DISABLE_NEWLINE_AUTO_RETURN` as well: | 6348 | .OPERATION_ABORTED => { |
| 6162 | // https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing | 6349 | try syscall.checkCancel(); |
| 6163 | // This is avoided because in the old Windows Console, that flag causes \n (as opposed to \r\n) | 6350 | continue; |
| 6164 | // to behave unexpectedly (the cursor moves down 1 row but remains on the same column). | 6351 | }, |
| 6165 | // Additionally, the default console mode in Windows Terminal does not have | 6352 | else => { |
| 6166 | // `DISABLE_NEWLINE_AUTO_RETURN` set, so by only enabling `ENABLE_VIRTUAL_TERMINAL_PROCESSING` | 6353 | syscall.finish(); |
| 6167 | // we end up matching the mode of Windows Terminal. | 6354 | if (try isCygwinPty(file)) return; |
| 6168 | const requested_console_modes = windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING; | 6355 | return error.NotTerminalDevice; |
| 6169 | const console_mode = original_console_mode | requested_console_modes; | 6356 | }, |
| 6170 | try Thread.checkCancel(); | 6357 | } |
| 6171 | if (windows.kernel32.SetConsoleMode(file.handle, console_mode) != 0) return; | | |
| 6172 | } | 6358 | } |
| 6173 | if (try isCygwinPty(file)) return; | 6359 | syscall.finish(); |
| 6174 | } else { | 6360 | } |
| 6175 | if (try supportsAnsiEscapeCodes(file)) return; | 6361 | |
| | 6362 | if (original_console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return; |
| | 6363 | |
| | 6364 | // For Windows Console, VT Sequences processing support was added in Windows 10 build 14361, but disabled by default. |
| | 6365 | // https://devblogs.microsoft.com/commandline/tmux-support-arrives-for-bash-on-ubuntu-on-windows/ |
| | 6366 | // |
| | 6367 | // Note: In Microsoft's example for enabling virtual terminal processing, it |
| | 6368 | // shows attempting to enable `DISABLE_NEWLINE_AUTO_RETURN` as well: |
| | 6369 | // https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing |
| | 6370 | // This is avoided because in the old Windows Console, that flag causes \n (as opposed to \r\n) |
| | 6371 | // to behave unexpectedly (the cursor moves down 1 row but remains on the same column). |
| | 6372 | // Additionally, the default console mode in Windows Terminal does not have |
| | 6373 | // `DISABLE_NEWLINE_AUTO_RETURN` set, so by only enabling `ENABLE_VIRTUAL_TERMINAL_PROCESSING` |
| | 6374 | // we end up matching the mode of Windows Terminal. |
| | 6375 | const requested_console_modes = windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING; |
| | 6376 | const console_mode = original_console_mode | requested_console_modes; |
| | 6377 | |
| | 6378 | { |
| | 6379 | const syscall: Syscall = try .start(); |
| | 6380 | while (windows.kernel32.SetConsoleMode(file.handle, console_mode) == 0) { |
| | 6381 | switch (windows.GetLastError()) { |
| | 6382 | .OPERATION_ABORTED => { |
| | 6383 | try syscall.checkCancel(); |
| | 6384 | continue; |
| | 6385 | }, |
| | 6386 | else => { |
| | 6387 | syscall.finish(); |
| | 6388 | if (try isCygwinPty(file)) return; |
| | 6389 | return error.NotTerminalDevice; |
| | 6390 | }, |
| | 6391 | } |
| | 6392 | } |
| | 6393 | syscall.finish(); |
| 6176 | } | 6394 | } |
| 6177 | return error.NotTerminalDevice; | | |
| 6178 | } | 6395 | } |
| 6179 | | 6396 | |
| 6180 | fn fileSupportsAnsiEscapeCodes(userdata: ?*anyopaque, file: File) Io.Cancelable!bool { | 6397 | fn fileSupportsAnsiEscapeCodes(userdata: ?*anyopaque, file: File) Io.Cancelable!bool { |
| ... | @@ -6185,11 +6402,27 @@ fn fileSupportsAnsiEscapeCodes(userdata: ?*anyopaque, file: File) Io.Cancelable! | ... | @@ -6185,11 +6402,27 @@ fn fileSupportsAnsiEscapeCodes(userdata: ?*anyopaque, file: File) Io.Cancelable! |
| 6185 | | 6402 | |
| 6186 | fn supportsAnsiEscapeCodes(file: File) Io.Cancelable!bool { | 6403 | fn supportsAnsiEscapeCodes(file: File) Io.Cancelable!bool { |
| 6187 | if (is_windows) { | 6404 | if (is_windows) { |
| 6188 | try Thread.checkCancel(); | | |
| 6189 | var console_mode: windows.DWORD = 0; | 6405 | var console_mode: windows.DWORD = 0; |
| 6190 | if (windows.kernel32.GetConsoleMode(file.handle, &console_mode) != 0) { | 6406 | |
| 6191 | if (console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return true; | 6407 | const syscall: Syscall = try .start(); |
| | 6408 | while (windows.kernel32.GetConsoleMode(file.handle, &console_mode) == 0) { |
| | 6409 | switch (windows.GetLastError()) { |
| | 6410 | .OPERATION_ABORTED => { |
| | 6411 | try syscall.checkCancel(); |
| | 6412 | continue; |
| | 6413 | }, |
| | 6414 | else => { |
| | 6415 | syscall.finish(); |
| | 6416 | break; |
| | 6417 | }, |
| | 6418 | } |
| | 6419 | } else { |
| | 6420 | syscall.finish(); |
| | 6421 | if (console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) { |
| | 6422 | return true; |
| | 6423 | } |
| 6192 | } | 6424 | } |
| | 6425 | |
| 6193 | return isCygwinPty(file); | 6426 | return isCygwinPty(file); |
| 6194 | } | 6427 | } |
| 6195 | | 6428 | |
| ... | @@ -6220,20 +6453,26 @@ fn isCygwinPty(file: File) Io.Cancelable!bool { | ... | @@ -6220,20 +6453,26 @@ fn isCygwinPty(file: File) Io.Cancelable!bool { |
| 6220 | // This allows us to avoid the more costly NtQueryInformationFile call | 6453 | // This allows us to avoid the more costly NtQueryInformationFile call |
| 6221 | // for handles that aren't named pipes. | 6454 | // for handles that aren't named pipes. |
| 6222 | { | 6455 | { |
| 6223 | try Thread.checkCancel(); | | |
| 6224 | var io_status: windows.IO_STATUS_BLOCK = undefined; | 6456 | var io_status: windows.IO_STATUS_BLOCK = undefined; |
| 6225 | var device_info: windows.FILE.FS_DEVICE_INFORMATION = undefined; | 6457 | var device_info: windows.FILE.FS_DEVICE_INFORMATION = undefined; |
| 6226 | const rc = windows.ntdll.NtQueryVolumeInformationFile( | 6458 | const syscall: Syscall = try .start(); |
| | 6459 | while (true) switch (windows.ntdll.NtQueryVolumeInformationFile( |
| 6227 | handle, | 6460 | handle, |
| 6228 | &io_status, | 6461 | &io_status, |
| 6229 | &device_info, | 6462 | &device_info, |
| 6230 | @sizeOf(windows.FILE.FS_DEVICE_INFORMATION), | 6463 | @sizeOf(windows.FILE.FS_DEVICE_INFORMATION), |
| 6231 | .Device, | 6464 | .Device, |
| 6232 | ); | 6465 | )) { |
| 6233 | switch (rc) { | 6466 | .SUCCESS => break syscall.finish(), |
| 6234 | .SUCCESS => {}, | 6467 | .CANCELLED => { |
| 6235 | else => return false, | 6468 | try syscall.checkCancel(); |
| 6236 | } | 6469 | continue; |
| | 6470 | }, |
| | 6471 | else => { |
| | 6472 | syscall.finish(); |
| | 6473 | return false; |
| | 6474 | }, |
| | 6475 | }; |
| 6237 | if (device_info.DeviceType.FileDevice != .NAMED_PIPE) return false; | 6476 | if (device_info.DeviceType.FileDevice != .NAMED_PIPE) return false; |
| 6238 | } | 6477 | } |
| 6239 | | 6478 | |
| ... | @@ -6248,19 +6487,25 @@ fn isCygwinPty(file: File) Io.Cancelable!bool { | ... | @@ -6248,19 +6487,25 @@ fn isCygwinPty(file: File) Io.Cancelable!bool { |
| 6248 | var name_info_bytes align(@alignOf(windows.FILE.NAME_INFORMATION)) = [_]u8{0} ** (name_bytes_offset + num_name_bytes); | 6487 | var name_info_bytes align(@alignOf(windows.FILE.NAME_INFORMATION)) = [_]u8{0} ** (name_bytes_offset + num_name_bytes); |
| 6249 | | 6488 | |
| 6250 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | 6489 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 6251 | try Thread.checkCancel(); | 6490 | const syscall: Syscall = try .start(); |
| 6252 | const rc = windows.ntdll.NtQueryInformationFile( | 6491 | while (true) switch (windows.ntdll.NtQueryInformationFile( |
| 6253 | handle, | 6492 | handle, |
| 6254 | &io_status_block, | 6493 | &io_status_block, |
| 6255 | &name_info_bytes, | 6494 | &name_info_bytes, |
| 6256 | @intCast(name_info_bytes.len), | 6495 | @intCast(name_info_bytes.len), |
| 6257 | .Name, | 6496 | .Name, |
| 6258 | ); | 6497 | )) { |
| 6259 | switch (rc) { | 6498 | .SUCCESS => break syscall.finish(), |
| 6260 | .SUCCESS => {}, | 6499 | .CANCELLED => { |
| | 6500 | try syscall.checkCancel(); |
| | 6501 | continue; |
| | 6502 | }, |
| 6261 | .INVALID_PARAMETER => unreachable, | 6503 | .INVALID_PARAMETER => unreachable, |
| 6262 | else => return false, | 6504 | else => { |
| 6263 | } | 6505 | syscall.finish(); |
| | 6506 | return false; |
| | 6507 | }, |
| | 6508 | }; |
| 6264 | | 6509 | |
| 6265 | const name_info: *const windows.FILE_NAME_INFO = @ptrCast(&name_info_bytes); | 6510 | const name_info: *const windows.FILE_NAME_INFO = @ptrCast(&name_info_bytes); |
| 6266 | const name_bytes = name_info_bytes[name_bytes_offset .. name_bytes_offset + name_info.FileNameLength]; | 6511 | const name_bytes = name_info_bytes[name_bytes_offset .. name_bytes_offset + name_info.FileNameLength]; |
| ... | @@ -6279,28 +6524,30 @@ fn fileSetLength(userdata: ?*anyopaque, file: File, length: u64) File.SetLengthE | ... | @@ -6279,28 +6524,30 @@ fn fileSetLength(userdata: ?*anyopaque, file: File, length: u64) File.SetLengthE |
| 6279 | if (signed_len < 0) return error.FileTooBig; // Avoid ambiguous EINVAL errors. | 6524 | if (signed_len < 0) return error.FileTooBig; // Avoid ambiguous EINVAL errors. |
| 6280 | | 6525 | |
| 6281 | if (is_windows) { | 6526 | if (is_windows) { |
| 6282 | try Thread.checkCancel(); | | |
| 6283 | | | |
| 6284 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | 6527 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 6285 | const eof_info: windows.FILE.END_OF_FILE_INFORMATION = .{ | 6528 | const eof_info: windows.FILE.END_OF_FILE_INFORMATION = .{ |
| 6286 | .EndOfFile = signed_len, | 6529 | .EndOfFile = signed_len, |
| 6287 | }; | 6530 | }; |
| 6288 | | 6531 | |
| 6289 | const status = windows.ntdll.NtSetInformationFile( | 6532 | const syscall: Syscall = try .start(); |
| | 6533 | while (true) switch (windows.ntdll.NtSetInformationFile( |
| 6290 | file.handle, | 6534 | file.handle, |
| 6291 | &io_status_block, | 6535 | &io_status_block, |
| 6292 | &eof_info, | 6536 | &eof_info, |
| 6293 | @sizeOf(windows.FILE.END_OF_FILE_INFORMATION), | 6537 | @sizeOf(windows.FILE.END_OF_FILE_INFORMATION), |
| 6294 | .EndOfFile, | 6538 | .EndOfFile, |
| 6295 | ); | 6539 | )) { |
| 6296 | switch (status) { | 6540 | .SUCCESS => return syscall.finish(), |
| 6297 | .SUCCESS => return, | 6541 | .CANCELLED => { |
| 6298 | .INVALID_HANDLE => |err| return windows.statusBug(err), // Handle not open for writing. | 6542 | try syscall.checkCancel(); |
| 6299 | .ACCESS_DENIED => return error.AccessDenied, | 6543 | continue; |
| 6300 | .USER_MAPPED_FILE => return error.AccessDenied, | 6544 | }, |
| 6301 | .INVALID_PARAMETER => return error.FileTooBig, | 6545 | .INVALID_HANDLE => |err| return syscall.ntstatusBug(err), // Handle not open for writing. |
| 6302 | else => return windows.unexpectedStatus(status), | 6546 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| 6303 | } | 6547 | .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied), |
| | 6548 | .INVALID_PARAMETER => return syscall.fail(error.FileTooBig), |
| | 6549 | else => |status| return syscall.unexpectedNtstatus(status), |
| | 6550 | }; |
| 6304 | } | 6551 | } |
| 6305 | | 6552 | |
| 6306 | if (native_os == .wasi and !builtin.link_libc) { | 6553 | if (native_os == .wasi and !builtin.link_libc) { |
| ... | @@ -6368,7 +6615,6 @@ fn fileSetPermissions(userdata: ?*anyopaque, file: File, permissions: File.Permi | ... | @@ -6368,7 +6615,6 @@ fn fileSetPermissions(userdata: ?*anyopaque, file: File, permissions: File.Permi |
| 6368 | _ = t; | 6615 | _ = t; |
| 6369 | switch (native_os) { | 6616 | switch (native_os) { |
| 6370 | .windows => { | 6617 | .windows => { |
| 6371 | try Thread.checkCancel(); | | |
| 6372 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | 6618 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 6373 | const info: windows.FILE.BASIC_INFORMATION = .{ | 6619 | const info: windows.FILE.BASIC_INFORMATION = .{ |
| 6374 | .CreationTime = 0, | 6620 | .CreationTime = 0, |
| ... | @@ -6377,19 +6623,23 @@ fn fileSetPermissions(userdata: ?*anyopaque, file: File, permissions: File.Permi | ... | @@ -6377,19 +6623,23 @@ fn fileSetPermissions(userdata: ?*anyopaque, file: File, permissions: File.Permi |
| 6377 | .ChangeTime = 0, | 6623 | .ChangeTime = 0, |
| 6378 | .FileAttributes = permissions.toAttributes(), | 6624 | .FileAttributes = permissions.toAttributes(), |
| 6379 | }; | 6625 | }; |
| 6380 | const status = windows.ntdll.NtSetInformationFile( | 6626 | const syscall: Syscall = try .start(); |
| | 6627 | while (true) switch (windows.ntdll.NtSetInformationFile( |
| 6381 | file.handle, | 6628 | file.handle, |
| 6382 | &io_status_block, | 6629 | &io_status_block, |
| 6383 | &info, | 6630 | &info, |
| 6384 | @sizeOf(windows.FILE.BASIC_INFORMATION), | 6631 | @sizeOf(windows.FILE.BASIC_INFORMATION), |
| 6385 | .Basic, | 6632 | .Basic, |
| 6386 | ); | 6633 | )) { |
| 6387 | switch (status) { | 6634 | .SUCCESS => return syscall.finish(), |
| 6388 | .SUCCESS => return, | 6635 | .CANCELLED => { |
| 6389 | .INVALID_HANDLE => |err| return windows.statusBug(err), | 6636 | try syscall.checkCancel(); |
| 6390 | .ACCESS_DENIED => return error.AccessDenied, | 6637 | continue; |
| 6391 | else => return windows.unexpectedStatus(status), | 6638 | }, |
| 6392 | } | 6639 | .INVALID_HANDLE => |err| return syscall.ntstatusBug(err), |
| | 6640 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| | 6641 | else => |status| return syscall.unexpectedNtstatus(status), |
| | 6642 | }; |
| 6393 | }, | 6643 | }, |
| 6394 | .wasi => return error.Unexpected, // Unsupported OS. | 6644 | .wasi => return error.Unexpected, // Unsupported OS. |
| 6395 | else => return setPermissionsPosix(file.handle, permissions.toMode()), | 6645 | else => return setPermissionsPosix(file.handle, permissions.toMode()), |
| ... | @@ -6484,8 +6734,6 @@ fn fileSetTimestamps( | ... | @@ -6484,8 +6734,6 @@ fn fileSetTimestamps( |
| 6484 | _ = t; | 6734 | _ = t; |
| 6485 | | 6735 | |
| 6486 | if (is_windows) { | 6736 | if (is_windows) { |
| 6487 | try Thread.checkCancel(); | | |
| 6488 | | | |
| 6489 | var access_time_buffer: windows.FILETIME = undefined; | 6737 | var access_time_buffer: windows.FILETIME = undefined; |
| 6490 | var modify_time_buffer: windows.FILETIME = undefined; | 6738 | var modify_time_buffer: windows.FILETIME = undefined; |
| 6491 | var system_time_buffer: windows.LARGE_INTEGER = undefined; | 6739 | var system_time_buffer: windows.LARGE_INTEGER = undefined; |
| ... | @@ -6513,13 +6761,22 @@ fn fileSetTimestamps( | ... | @@ -6513,13 +6761,22 @@ fn fileSetTimestamps( |
| 6513 | }; | 6761 | }; |
| 6514 | | 6762 | |
| 6515 | // https://github.com/ziglang/zig/issues/1840 | 6763 | // https://github.com/ziglang/zig/issues/1840 |
| 6516 | const rc = windows.kernel32.SetFileTime(file.handle, null, access_ptr, modify_ptr); | 6764 | const syscall: Syscall = try .start(); |
| 6517 | if (rc == 0) { | 6765 | while (true) { |
| 6518 | switch (windows.GetLastError()) { | 6766 | switch (windows.kernel32.SetFileTime(file.handle, null, access_ptr, modify_ptr)) { |
| 6519 | else => |err| return windows.unexpectedError(err), | 6767 | 0 => switch (windows.GetLastError()) { |
| | 6768 | .OPERATION_ABORTED => { |
| | 6769 | try syscall.checkCancel(); |
| | 6770 | continue; |
| | 6771 | }, |
| | 6772 | else => |err| { |
| | 6773 | syscall.finish(); |
| | 6774 | return windows.unexpectedError(err); |
| | 6775 | }, |
| | 6776 | }, |
| | 6777 | else => return syscall.finish(), |
| 6520 | } | 6778 | } |
| 6521 | } | 6779 | } |
| 6522 | return; | | |
| 6523 | } | 6780 | } |
| 6524 | | 6781 | |
| 6525 | if (native_os == .wasi and !builtin.link_libc) { | 6782 | if (native_os == .wasi and !builtin.link_libc) { |
| ... | @@ -6601,27 +6858,26 @@ fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!v | ... | @@ -6601,27 +6858,26 @@ fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!v |
| 6601 | .none => { | 6858 | .none => { |
| 6602 | // To match the non-Windows behavior, unlock | 6859 | // To match the non-Windows behavior, unlock |
| 6603 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | 6860 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 6604 | const status = windows.ntdll.NtUnlockFile( | 6861 | while (true) switch (windows.ntdll.NtUnlockFile( |
| 6605 | file.handle, | 6862 | file.handle, |
| 6606 | &io_status_block, | 6863 | &io_status_block, |
| 6607 | &windows_lock_range_off, | 6864 | &windows_lock_range_off, |
| 6608 | &windows_lock_range_len, | 6865 | &windows_lock_range_len, |
| 6609 | 0, | 6866 | 0, |
| 6610 | ); | 6867 | )) { |
| 6611 | switch (status) { | 6868 | .SUCCESS => return, |
| 6612 | .SUCCESS => {}, | 6869 | .CANCELLED => continue, |
| 6613 | .RANGE_NOT_LOCKED => {}, | 6870 | .RANGE_NOT_LOCKED => return, |
| 6614 | .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer | 6871 | .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer |
| 6615 | else => return windows.unexpectedStatus(status), | 6872 | else => |status| return windows.unexpectedStatus(status), |
| 6616 | } | 6873 | }; |
| 6617 | return; | | |
| 6618 | }, | 6874 | }, |
| 6619 | .shared => false, | 6875 | .shared => false, |
| 6620 | .exclusive => true, | 6876 | .exclusive => true, |
| 6621 | }; | 6877 | }; |
| 6622 | try Thread.checkCancel(); | | |
| 6623 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | 6878 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 6624 | const status = windows.ntdll.NtLockFile( | 6879 | const syscall: Syscall = try .start(); |
| | 6880 | while (true) switch (windows.ntdll.NtLockFile( |
| 6625 | file.handle, | 6881 | file.handle, |
| 6626 | null, | 6882 | null, |
| 6627 | null, | 6883 | null, |
| ... | @@ -6632,14 +6888,17 @@ fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!v | ... | @@ -6632,14 +6888,17 @@ fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!v |
| 6632 | null, | 6888 | null, |
| 6633 | windows.FALSE, | 6889 | windows.FALSE, |
| 6634 | @intFromBool(exclusive), | 6890 | @intFromBool(exclusive), |
| 6635 | ); | 6891 | )) { |
| 6636 | switch (status) { | 6892 | .SUCCESS => return syscall.finish(), |
| 6637 | .SUCCESS => return, | 6893 | .CANCELLED => { |
| 6638 | .INSUFFICIENT_RESOURCES => return error.SystemResources, | 6894 | try syscall.checkCancel(); |
| 6639 | .LOCK_NOT_GRANTED => |err| return windows.statusBug(err), // passed FailImmediately=false | 6895 | continue; |
| 6640 | .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer | 6896 | }, |
| 6641 | else => return windows.unexpectedStatus(status), | 6897 | .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources), |
| 6642 | } | 6898 | .LOCK_NOT_GRANTED => |err| return syscall.ntstatusBug(err), // passed FailImmediately=false |
| | 6899 | .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer |
| | 6900 | else => |status| return syscall.unexpectedNtstatus(status), |
| | 6901 | }; |
| 6643 | } | 6902 | } |
| 6644 | | 6903 | |
| 6645 | const operation: i32 = switch (lock) { | 6904 | const operation: i32 = switch (lock) { |
| ... | @@ -6680,26 +6939,26 @@ fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockErro | ... | @@ -6680,26 +6939,26 @@ fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockErro |
| 6680 | .none => { | 6939 | .none => { |
| 6681 | // To match the non-Windows behavior, unlock | 6940 | // To match the non-Windows behavior, unlock |
| 6682 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | 6941 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 6683 | const status = windows.ntdll.NtUnlockFile( | 6942 | while (true) switch (windows.ntdll.NtUnlockFile( |
| 6684 | file.handle, | 6943 | file.handle, |
| 6685 | &io_status_block, | 6944 | &io_status_block, |
| 6686 | &windows_lock_range_off, | 6945 | &windows_lock_range_off, |
| 6687 | &windows_lock_range_len, | 6946 | &windows_lock_range_len, |
| 6688 | 0, | 6947 | 0, |
| 6689 | ); | 6948 | )) { |
| 6690 | switch (status) { | | |
| 6691 | .SUCCESS => return true, | 6949 | .SUCCESS => return true, |
| | 6950 | .CANCELLED => continue, |
| 6692 | .RANGE_NOT_LOCKED => return false, | 6951 | .RANGE_NOT_LOCKED => return false, |
| 6693 | .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer | 6952 | .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer |
| 6694 | else => return windows.unexpectedStatus(status), | 6953 | else => |status| return windows.unexpectedStatus(status), |
| 6695 | } | 6954 | }; |
| 6696 | }, | 6955 | }, |
| 6697 | .shared => false, | 6956 | .shared => false, |
| 6698 | .exclusive => true, | 6957 | .exclusive => true, |
| 6699 | }; | 6958 | }; |
| 6700 | try Thread.checkCancel(); | | |
| 6701 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | 6959 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 6702 | const status = windows.ntdll.NtLockFile( | 6960 | const syscall: Syscall = try .start(); |
| | 6961 | while (true) switch (windows.ntdll.NtLockFile( |
| 6703 | file.handle, | 6962 | file.handle, |
| 6704 | null, | 6963 | null, |
| 6705 | null, | 6964 | null, |
| ... | @@ -6710,14 +6969,23 @@ fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockErro | ... | @@ -6710,14 +6969,23 @@ fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockErro |
| 6710 | null, | 6969 | null, |
| 6711 | windows.TRUE, | 6970 | windows.TRUE, |
| 6712 | @intFromBool(exclusive), | 6971 | @intFromBool(exclusive), |
| 6713 | ); | 6972 | )) { |
| 6714 | switch (status) { | 6973 | .SUCCESS => { |
| 6715 | .SUCCESS => return true, | 6974 | syscall.finish(); |
| 6716 | .INSUFFICIENT_RESOURCES => return error.SystemResources, | 6975 | return true; |
| 6717 | .LOCK_NOT_GRANTED => return false, | 6976 | }, |
| 6718 | .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer | 6977 | .LOCK_NOT_GRANTED => { |
| 6719 | else => return windows.unexpectedStatus(status), | 6978 | syscall.finish(); |
| 6720 | } | 6979 | return false; |
| | 6980 | }, |
| | 6981 | .CANCELLED => { |
| | 6982 | try syscall.checkCancel(); |
| | 6983 | continue; |
| | 6984 | }, |
| | 6985 | .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources), |
| | 6986 | .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer |
| | 6987 | else => |status| return syscall.unexpectedNtstatus(status), |
| | 6988 | }; |
| 6721 | } | 6989 | } |
| 6722 | | 6990 | |
| 6723 | const operation: i32 = switch (lock) { | 6991 | const operation: i32 = switch (lock) { |
| ... | @@ -6761,20 +7029,19 @@ fn fileUnlock(userdata: ?*anyopaque, file: File) void { | ... | @@ -6761,20 +7029,19 @@ fn fileUnlock(userdata: ?*anyopaque, file: File) void { |
| 6761 | | 7029 | |
| 6762 | if (is_windows) { | 7030 | if (is_windows) { |
| 6763 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | 7031 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 6764 | const status = windows.ntdll.NtUnlockFile( | 7032 | while (true) switch (windows.ntdll.NtUnlockFile( |
| 6765 | file.handle, | 7033 | file.handle, |
| 6766 | &io_status_block, | 7034 | &io_status_block, |
| 6767 | &windows_lock_range_off, | 7035 | &windows_lock_range_off, |
| 6768 | &windows_lock_range_len, | 7036 | &windows_lock_range_len, |
| 6769 | 0, | 7037 | 0, |
| 6770 | ); | 7038 | )) { |
| 6771 | if (is_debug) switch (status) { | 7039 | .SUCCESS => return, |
| 6772 | .SUCCESS => {}, | 7040 | .CANCELLED => continue, |
| 6773 | .RANGE_NOT_LOCKED => unreachable, // Function asserts unlocked. | 7041 | .RANGE_NOT_LOCKED => if (is_debug) unreachable else return, // Function asserts unlocked. |
| 6774 | .ACCESS_VIOLATION => unreachable, // bad io_status_block pointer | 7042 | .ACCESS_VIOLATION => if (is_debug) unreachable else return, // bad io_status_block pointer |
| 6775 | else => unreachable, // Resource deallocation must succeed. | 7043 | else => if (is_debug) unreachable else return, // Resource deallocation must succeed. |
| 6776 | }; | 7044 | }; |
| 6777 | return; | | |
| 6778 | } | 7045 | } |
| 6779 | | 7046 | |
| 6780 | while (true) { | 7047 | while (true) { |
| ... | @@ -6797,14 +7064,14 @@ fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError! | ... | @@ -6797,14 +7064,14 @@ fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError! |
| 6797 | _ = t; | 7064 | _ = t; |
| 6798 | | 7065 | |
| 6799 | if (is_windows) { | 7066 | if (is_windows) { |
| 6800 | try Thread.checkCancel(); | | |
| 6801 | // On Windows it works like a semaphore + exclusivity flag. To | 7067 | // On Windows it works like a semaphore + exclusivity flag. To |
| 6802 | // implement this function, we first obtain another lock in shared | 7068 | // implement this function, we first obtain another lock in shared |
| 6803 | // mode. This changes the exclusivity flag, but increments the | 7069 | // mode. This changes the exclusivity flag, but increments the |
| 6804 | // semaphore to 2. So we follow up with an NtUnlockFile which | 7070 | // semaphore to 2. So we follow up with an NtUnlockFile which |
| 6805 | // decrements the semaphore but does not modify the exclusivity flag. | 7071 | // decrements the semaphore but does not modify the exclusivity flag. |
| 6806 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | 7072 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 6807 | switch (windows.ntdll.NtLockFile( | 7073 | const syscall: Syscall = try .start(); |
| | 7074 | while (true) switch (windows.ntdll.NtLockFile( |
| 6808 | file.handle, | 7075 | file.handle, |
| 6809 | null, | 7076 | null, |
| 6810 | null, | 7077 | null, |
| ... | @@ -6816,26 +7083,29 @@ fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError! | ... | @@ -6816,26 +7083,29 @@ fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError! |
| 6816 | windows.TRUE, | 7083 | windows.TRUE, |
| 6817 | windows.FALSE, | 7084 | windows.FALSE, |
| 6818 | )) { | 7085 | )) { |
| 6819 | .SUCCESS => {}, | 7086 | .SUCCESS => break syscall.finish(), |
| 6820 | .INSUFFICIENT_RESOURCES => |err| return windows.statusBug(err), | 7087 | .CANCELLED => { |
| 6821 | .LOCK_NOT_GRANTED => |err| return windows.statusBug(err), // File was not locked in exclusive mode. | 7088 | try syscall.checkCancel(); |
| 6822 | .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer | 7089 | continue; |
| 6823 | else => |status| return windows.unexpectedStatus(status), | 7090 | }, |
| 6824 | } | 7091 | .INSUFFICIENT_RESOURCES => |err| return syscall.ntstatusBug(err), |
| 6825 | const status = windows.ntdll.NtUnlockFile( | 7092 | .LOCK_NOT_GRANTED => |err| return syscall.ntstatusBug(err), // File was not locked in exclusive mode. |
| | 7093 | .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer |
| | 7094 | else => |status| return syscall.unexpectedNtstatus(status), |
| | 7095 | }; |
| | 7096 | while (true) switch (windows.ntdll.NtUnlockFile( |
| 6826 | file.handle, | 7097 | file.handle, |
| 6827 | &io_status_block, | 7098 | &io_status_block, |
| 6828 | &windows_lock_range_off, | 7099 | &windows_lock_range_off, |
| 6829 | &windows_lock_range_len, | 7100 | &windows_lock_range_len, |
| 6830 | 0, | 7101 | 0, |
| 6831 | ); | 7102 | )) { |
| 6832 | if (is_debug) switch (status) { | 7103 | .SUCCESS => return, |
| 6833 | .SUCCESS => {}, | 7104 | .CANCELLED => continue, |
| 6834 | .RANGE_NOT_LOCKED => unreachable, // File was not locked. | 7105 | .RANGE_NOT_LOCKED => if (is_debug) unreachable else return, // File was not locked. |
| 6835 | .ACCESS_VIOLATION => unreachable, // bad io_status_block pointer | 7106 | .ACCESS_VIOLATION => if (is_debug) unreachable else return, // bad io_status_block pointer |
| 6836 | else => unreachable, // Resource deallocation must succeed. | 7107 | else => if (is_debug) unreachable else return, // Resource deallocation must succeed. |
| 6837 | }; | 7108 | }; |
| 6838 | return; | | |
| 6839 | } | 7109 | } |
| 6840 | | 7110 | |
| 6841 | const operation = posix.LOCK.SH | posix.LOCK.NB; | 7111 | const operation = posix.LOCK.SH | posix.LOCK.NB; |
| ... | @@ -7158,21 +7428,34 @@ fn fileReadStreamingWindows(userdata: ?*anyopaque, file: File, data: []const []u | ... | @@ -7158,21 +7428,34 @@ fn fileReadStreamingWindows(userdata: ?*anyopaque, file: File, data: []const []u |
| 7158 | const buffer = data[index]; | 7428 | const buffer = data[index]; |
| 7159 | const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len); | 7429 | const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len); |
| 7160 | | 7430 | |
| | 7431 | const syscall: Syscall = try .start(); |
| 7161 | while (true) { | 7432 | while (true) { |
| 7162 | try Thread.checkCancel(); | | |
| 7163 | var n: DWORD = undefined; | 7433 | var n: DWORD = undefined; |
| 7164 | if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) != 0) | 7434 | if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) != 0) { |
| | 7435 | syscall.finish(); |
| 7165 | return n; | 7436 | return n; |
| | 7437 | } |
| 7166 | switch (windows.GetLastError()) { | 7438 | switch (windows.GetLastError()) { |
| 7167 | .IO_PENDING => |err| return windows.errorBug(err), | 7439 | .IO_PENDING => |err| { |
| 7168 | .OPERATION_ABORTED => continue, | 7440 | syscall.finish(); |
| 7169 | .BROKEN_PIPE => return 0, | 7441 | return windows.errorBug(err); |
| 7170 | .HANDLE_EOF => return 0, | 7442 | }, |
| 7171 | .NETNAME_DELETED => return error.ConnectionResetByPeer, | 7443 | .OPERATION_ABORTED => { |
| 7172 | .LOCK_VIOLATION => return error.LockViolation, | 7444 | try syscall.checkCancel(); |
| 7173 | .ACCESS_DENIED => return error.AccessDenied, | 7445 | continue; |
| 7174 | .INVALID_HANDLE => return error.NotOpenForReading, | 7446 | }, |
| 7175 | else => |err| return windows.unexpectedError(err), | 7447 | .BROKEN_PIPE, .HANDLE_EOF => { |
| | 7448 | syscall.finish(); |
| | 7449 | return 0; |
| | 7450 | }, |
| | 7451 | .NETNAME_DELETED => return syscall.fail(error.ConnectionResetByPeer), |
| | 7452 | .LOCK_VIOLATION => return syscall.fail(error.LockViolation), |
| | 7453 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| | 7454 | .INVALID_HANDLE => return syscall.fail(error.NotOpenForReading), |
| | 7455 | else => |err| { |
| | 7456 | syscall.finish(); |
| | 7457 | return windows.unexpectedError(err); |
| | 7458 | }, |
| 7176 | } | 7459 | } |
| 7177 | } | 7460 | } |
| 7178 | } | 7461 | } |
| ... | @@ -7302,21 +7585,34 @@ fn fileReadPositionalWindows(userdata: ?*anyopaque, file: File, data: []const [] | ... | @@ -7302,21 +7585,34 @@ fn fileReadPositionalWindows(userdata: ?*anyopaque, file: File, data: []const [] |
| 7302 | .hEvent = null, | 7585 | .hEvent = null, |
| 7303 | }; | 7586 | }; |
| 7304 | | 7587 | |
| | 7588 | const syscall: Syscall = try .start(); |
| 7305 | while (true) { | 7589 | while (true) { |
| 7306 | try Thread.checkCancel(); | | |
| 7307 | var n: DWORD = undefined; | 7590 | var n: DWORD = undefined; |
| 7308 | if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, &overlapped) != 0) | 7591 | if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, &overlapped) != 0) { |
| | 7592 | syscall.finish(); |
| 7309 | return n; | 7593 | return n; |
| | 7594 | } |
| 7310 | switch (windows.GetLastError()) { | 7595 | switch (windows.GetLastError()) { |
| 7311 | .IO_PENDING => |err| return windows.errorBug(err), | 7596 | .IO_PENDING => |err| { |
| 7312 | .OPERATION_ABORTED => continue, | 7597 | syscall.finish(); |
| 7313 | .BROKEN_PIPE => return 0, | 7598 | return windows.errorBug(err); |
| 7314 | .HANDLE_EOF => return 0, | 7599 | }, |
| 7315 | .NETNAME_DELETED => return error.ConnectionResetByPeer, | 7600 | .OPERATION_ABORTED => { |
| 7316 | .LOCK_VIOLATION => return error.LockViolation, | 7601 | try syscall.checkCancel(); |
| 7317 | .ACCESS_DENIED => return error.AccessDenied, | 7602 | continue; |
| 7318 | .INVALID_HANDLE => return error.NotOpenForReading, | 7603 | }, |
| 7319 | else => |err| return windows.unexpectedError(err), | 7604 | .BROKEN_PIPE, .HANDLE_EOF => { |
| | 7605 | syscall.finish(); |
| | 7606 | return 0; |
| | 7607 | }, |
| | 7608 | .NETNAME_DELETED => return syscall.fail(error.ConnectionResetByPeer), |
| | 7609 | .LOCK_VIOLATION => return syscall.fail(error.LockViolation), |
| | 7610 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| | 7611 | .INVALID_HANDLE => return syscall.fail(error.NotOpenForReading), |
| | 7612 | else => |err| { |
| | 7613 | syscall.finish(); |
| | 7614 | return windows.unexpectedError(err); |
| | 7615 | }, |
| 7320 | } | 7616 | } |
| 7321 | } | 7617 | } |
| 7322 | } | 7618 | } |
| ... | @@ -7355,8 +7651,26 @@ fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!voi | ... | @@ -7355,8 +7651,26 @@ fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!voi |
| 7355 | } | 7651 | } |
| 7356 | | 7652 | |
| 7357 | if (native_os == .windows) { | 7653 | if (native_os == .windows) { |
| 7358 | try Thread.checkCancel(); | 7654 | const syscall: Syscall = try .start(); |
| 7359 | return windows.SetFilePointerEx_CURRENT(fd, offset); | 7655 | while (true) { |
| | 7656 | if (windows.kernel32.SetFilePointerEx(fd, offset, null, windows.FILE_CURRENT) != 0) { |
| | 7657 | return syscall.finish(); |
| | 7658 | } |
| | 7659 | switch (windows.GetLastError()) { |
| | 7660 | .OPERATION_ABORTED => { |
| | 7661 | try syscall.checkCancel(); |
| | 7662 | continue; |
| | 7663 | }, |
| | 7664 | .INVALID_FUNCTION => return syscall.fail(error.Unseekable), |
| | 7665 | .NEGATIVE_SEEK => return syscall.fail(error.Unseekable), |
| | 7666 | .INVALID_PARAMETER => unreachable, |
| | 7667 | .INVALID_HANDLE => unreachable, |
| | 7668 | else => |err| { |
| | 7669 | syscall.finish(); |
| | 7670 | return windows.unexpectedError(err); |
| | 7671 | }, |
| | 7672 | } |
| | 7673 | } |
| 7360 | } | 7674 | } |
| 7361 | | 7675 | |
| 7362 | if (native_os == .wasi and !builtin.link_libc) { | 7676 | if (native_os == .wasi and !builtin.link_libc) { |
| ... | @@ -7422,8 +7736,31 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!voi | ... | @@ -7422,8 +7736,31 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!voi |
| 7422 | const fd = file.handle; | 7736 | const fd = file.handle; |
| 7423 | | 7737 | |
| 7424 | if (native_os == .windows) { | 7738 | if (native_os == .windows) { |
| 7425 | try Thread.checkCancel(); | 7739 | // "The starting point is zero or the beginning of the file. If [FILE_BEGIN] |
| 7426 | return windows.SetFilePointerEx_BEGIN(fd, offset); | 7740 | // is specified, then the liDistanceToMove parameter is interpreted as an unsigned value." |
| | 7741 | // https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-setfilepointerex |
| | 7742 | const ipos: windows.LARGE_INTEGER = @bitCast(offset); |
| | 7743 | |
| | 7744 | const syscall: Syscall = try .start(); |
| | 7745 | while (true) { |
| | 7746 | if (windows.kernel32.SetFilePointerEx(fd, ipos, null, windows.FILE_BEGIN) != 0) { |
| | 7747 | return syscall.finish(); |
| | 7748 | } |
| | 7749 | switch (windows.GetLastError()) { |
| | 7750 | .OPERATION_ABORTED => { |
| | 7751 | try syscall.checkCancel(); |
| | 7752 | continue; |
| | 7753 | }, |
| | 7754 | .INVALID_FUNCTION => return syscall.fail(error.Unseekable), |
| | 7755 | .NEGATIVE_SEEK => return syscall.fail(error.Unseekable), |
| | 7756 | .INVALID_PARAMETER => unreachable, |
| | 7757 | .INVALID_HANDLE => unreachable, |
| | 7758 | else => |err| { |
| | 7759 | syscall.finish(); |
| | 7760 | return windows.unexpectedError(err); |
| | 7761 | }, |
| | 7762 | } |
| | 7763 | } |
| 7427 | } | 7764 | } |
| 7428 | | 7765 | |
| 7429 | if (native_os == .wasi and !builtin.link_libc) { | 7766 | if (native_os == .wasi and !builtin.link_libc) { |
| ... | @@ -7527,7 +7864,7 @@ fn processExecutableOpen(userdata: ?*anyopaque, flags: File.OpenFlags) std.proce | ... | @@ -7527,7 +7864,7 @@ fn processExecutableOpen(userdata: ?*anyopaque, flags: File.OpenFlags) std.proce |
| 7527 | const image_path_unicode_string = &windows.peb().ProcessParameters.ImagePathName; | 7864 | const image_path_unicode_string = &windows.peb().ProcessParameters.ImagePathName; |
| 7528 | const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0]; | 7865 | const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0]; |
| 7529 | const prefixed_path_w = try windows.wToPrefixedFileW(null, image_path_name); | 7866 | const prefixed_path_w = try windows.wToPrefixedFileW(null, image_path_name); |
| 7530 | return dirOpenFileWtf16(t, null, prefixed_path_w.span(), flags); | 7867 | return dirOpenFileWtf16(null, prefixed_path_w.span(), flags); |
| 7531 | }, | 7868 | }, |
| 7532 | .driverkit, | 7869 | .driverkit, |
| 7533 | .ios, | 7870 | .ios, |
| ... | @@ -7736,7 +8073,6 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex | ... | @@ -7736,7 +8073,6 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex |
| 7736 | return error.FileNotFound; | 8073 | return error.FileNotFound; |
| 7737 | }, | 8074 | }, |
| 7738 | .windows => { | 8075 | .windows => { |
| 7739 | try Thread.checkCancel(); | | |
| 7740 | const w = windows; | 8076 | const w = windows; |
| 7741 | const image_path_unicode_string = &w.peb().ProcessParameters.ImagePathName; | 8077 | const image_path_unicode_string = &w.peb().ProcessParameters.ImagePathName; |
| 7742 | const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0]; | 8078 | const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0]; |
| ... | @@ -7746,24 +8082,34 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex | ... | @@ -7746,24 +8082,34 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex |
| 7746 | // that the symlink points to, though, so we need to get the realpath. | 8082 | // that the symlink points to, though, so we need to get the realpath. |
| 7747 | var path_name_w_buf = try w.wToPrefixedFileW(null, image_path_name); | 8083 | var path_name_w_buf = try w.wToPrefixedFileW(null, image_path_name); |
| 7748 | | 8084 | |
| 7749 | const h_file = blk: { | 8085 | const h_file = handle: { |
| 7750 | const res = w.OpenFile(path_name_w_buf.span(), .{ | 8086 | const syscall: Syscall = try .start(); |
| 7751 | .dir = null, | 8087 | while (true) { |
| 7752 | .access_mask = .{ | 8088 | if (w.OpenFile(path_name_w_buf.span(), .{ |
| 7753 | .GENERIC = .{ .READ = true }, | 8089 | .dir = null, |
| 7754 | .STANDARD = .{ .SYNCHRONIZE = true }, | 8090 | .access_mask = .{ |
| 7755 | }, | 8091 | .GENERIC = .{ .READ = true }, |
| 7756 | .creation = .OPEN, | 8092 | .STANDARD = .{ .SYNCHRONIZE = true }, |
| 7757 | .filter = .any, | 8093 | }, |
| 7758 | }) catch |err| switch (err) { | 8094 | .creation = .OPEN, |
| 7759 | error.WouldBlock => unreachable, | 8095 | .filter = .any, |
| 7760 | else => |e| return e, | 8096 | })) |handle| { |
| 7761 | }; | 8097 | syscall.finish(); |
| 7762 | break :blk res; | 8098 | break :handle handle; |
| | 8099 | } else |err| switch (err) { |
| | 8100 | error.WouldBlock => unreachable, |
| | 8101 | error.OperationCanceled => { |
| | 8102 | try syscall.checkCancel(); |
| | 8103 | continue; |
| | 8104 | }, |
| | 8105 | else => |e| return e, |
| | 8106 | } |
| | 8107 | } |
| 7763 | }; | 8108 | }; |
| 7764 | defer w.CloseHandle(h_file); | 8109 | defer w.CloseHandle(h_file); |
| 7765 | | 8110 | |
| 7766 | // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks | 8111 | // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks |
| | 8112 | try Thread.checkCancel(); |
| 7767 | const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, &path_name_w_buf.data); | 8113 | const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, &path_name_w_buf.data); |
| 7768 | | 8114 | |
| 7769 | const len = std.unicode.calcWtf8Len(wide_slice); | 8115 | const len = std.unicode.calcWtf8Len(wide_slice); |
| ... | @@ -7916,8 +8262,6 @@ fn writeFilePositionalWindows( | ... | @@ -7916,8 +8262,6 @@ fn writeFilePositionalWindows( |
| 7916 | bytes: []const u8, | 8262 | bytes: []const u8, |
| 7917 | offset: u64, | 8263 | offset: u64, |
| 7918 | ) File.WritePositionalError!usize { | 8264 | ) File.WritePositionalError!usize { |
| 7919 | try Thread.checkCancel(); | | |
| 7920 | | | |
| 7921 | var bytes_written: windows.DWORD = undefined; | 8265 | var bytes_written: windows.DWORD = undefined; |
| 7922 | var overlapped: windows.OVERLAPPED = .{ | 8266 | var overlapped: windows.OVERLAPPED = .{ |
| 7923 | .Internal = 0, | 8267 | .Internal = 0, |
| ... | @@ -7931,21 +8275,31 @@ fn writeFilePositionalWindows( | ... | @@ -7931,21 +8275,31 @@ fn writeFilePositionalWindows( |
| 7931 | .hEvent = null, | 8275 | .hEvent = null, |
| 7932 | }; | 8276 | }; |
| 7933 | const adjusted_len = std.math.lossyCast(u32, bytes.len); | 8277 | const adjusted_len = std.math.lossyCast(u32, bytes.len); |
| 7934 | if (windows.kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, &overlapped) == 0) { | 8278 | const syscall: Syscall = try .start(); |
| | 8279 | while (true) { |
| | 8280 | if (windows.kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, &overlapped) != 0) { |
| | 8281 | syscall.finish(); |
| | 8282 | return bytes_written; |
| | 8283 | } |
| 7935 | switch (windows.GetLastError()) { | 8284 | switch (windows.GetLastError()) { |
| 7936 | .INVALID_USER_BUFFER => return error.SystemResources, | 8285 | .OPERATION_ABORTED => { |
| 7937 | .NOT_ENOUGH_MEMORY => return error.SystemResources, | 8286 | try syscall.checkCancel(); |
| 7938 | .OPERATION_ABORTED => return error.Canceled, | 8287 | continue; |
| 7939 | .NOT_ENOUGH_QUOTA => return error.SystemResources, | 8288 | }, |
| 7940 | .NO_DATA => return error.BrokenPipe, | 8289 | .INVALID_USER_BUFFER => return syscall.fail(error.SystemResources), |
| 7941 | .INVALID_HANDLE => return error.NotOpenForWriting, | 8290 | .NOT_ENOUGH_MEMORY => return syscall.fail(error.SystemResources), |
| 7942 | .LOCK_VIOLATION => return error.LockViolation, | 8291 | .NOT_ENOUGH_QUOTA => return syscall.fail(error.SystemResources), |
| 7943 | .ACCESS_DENIED => return error.AccessDenied, | 8292 | .NO_DATA => return syscall.fail(error.BrokenPipe), |
| 7944 | .WORKING_SET_QUOTA => return error.SystemResources, | 8293 | .INVALID_HANDLE => return syscall.fail(error.NotOpenForWriting), |
| 7945 | else => |err| return windows.unexpectedError(err), | 8294 | .LOCK_VIOLATION => return syscall.fail(error.LockViolation), |
| | 8295 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| | 8296 | .WORKING_SET_QUOTA => return syscall.fail(error.SystemResources), |
| | 8297 | else => |err| { |
| | 8298 | syscall.finish(); |
| | 8299 | return windows.unexpectedError(err); |
| | 8300 | }, |
| 7946 | } | 8301 | } |
| 7947 | } | 8302 | } |
| 7948 | return bytes_written; | | |
| 7949 | } | 8303 | } |
| 7950 | | 8304 | |
| 7951 | fn fileWriteStreaming( | 8305 | fn fileWriteStreaming( |
| ... | @@ -8078,25 +8432,33 @@ fn writeFileStreamingWindows( | ... | @@ -8078,25 +8432,33 @@ fn writeFileStreamingWindows( |
| 8078 | handle: windows.HANDLE, | 8432 | handle: windows.HANDLE, |
| 8079 | bytes: []const u8, | 8433 | bytes: []const u8, |
| 8080 | ) File.Writer.Error!usize { | 8434 | ) File.Writer.Error!usize { |
| 8081 | try Thread.checkCancel(); | | |
| 8082 | | | |
| 8083 | var bytes_written: windows.DWORD = undefined; | 8435 | var bytes_written: windows.DWORD = undefined; |
| 8084 | const adjusted_len = std.math.lossyCast(u32, bytes.len); | 8436 | const adjusted_len = std.math.lossyCast(u32, bytes.len); |
| 8085 | if (windows.kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, null) == 0) { | 8437 | const syscall: Syscall = try .start(); |
| | 8438 | while (true) { |
| | 8439 | if (windows.kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, null) != 0) { |
| | 8440 | syscall.finish(); |
| | 8441 | return bytes_written; |
| | 8442 | } |
| 8086 | switch (windows.GetLastError()) { | 8443 | switch (windows.GetLastError()) { |
| 8087 | .INVALID_USER_BUFFER => return error.SystemResources, | 8444 | .OPERATION_ABORTED => { |
| 8088 | .NOT_ENOUGH_MEMORY => return error.SystemResources, | 8445 | try syscall.checkCancel(); |
| 8089 | .OPERATION_ABORTED => return error.Canceled, | 8446 | continue; |
| 8090 | .NOT_ENOUGH_QUOTA => return error.SystemResources, | 8447 | }, |
| 8091 | .NO_DATA => return error.BrokenPipe, | 8448 | .INVALID_USER_BUFFER => return syscall.fail(error.SystemResources), |
| 8092 | .INVALID_HANDLE => return error.NotOpenForWriting, | 8449 | .NOT_ENOUGH_MEMORY => return syscall.fail(error.SystemResources), |
| 8093 | .LOCK_VIOLATION => return error.LockViolation, | 8450 | .NOT_ENOUGH_QUOTA => return syscall.fail(error.SystemResources), |
| 8094 | .ACCESS_DENIED => return error.AccessDenied, | 8451 | .NO_DATA => return syscall.fail(error.BrokenPipe), |
| 8095 | .WORKING_SET_QUOTA => return error.SystemResources, | 8452 | .INVALID_HANDLE => return syscall.fail(error.NotOpenForWriting), |
| 8096 | else => |err| return windows.unexpectedError(err), | 8453 | .LOCK_VIOLATION => return syscall.fail(error.LockViolation), |
| | 8454 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| | 8455 | .WORKING_SET_QUOTA => return syscall.fail(error.SystemResources), |
| | 8456 | else => |err| { |
| | 8457 | syscall.finish(); |
| | 8458 | return windows.unexpectedError(err); |
| | 8459 | }, |
| 8097 | } | 8460 | } |
| 8098 | } | 8461 | } |
| 8099 | return bytes_written; | | |
| 8100 | } | 8462 | } |
| 8101 | | 8463 | |
| 8102 | fn fileWriteFileStreaming( | 8464 | fn fileWriteFileStreaming( |
| ... | @@ -8716,9 +9078,7 @@ fn fileWriteFilePositional( | ... | @@ -8716,9 +9078,7 @@ fn fileWriteFilePositional( |
| 8716 | return error.Unimplemented; | 9078 | return error.Unimplemented; |
| 8717 | } | 9079 | } |
| 8718 | | 9080 | |
| 8719 | fn nowPosix(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp { | 9081 | fn nowPosix(clock: Io.Clock) Io.Clock.Error!Io.Timestamp { |
| 8720 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | | |
| 8721 | _ = t; | | |
| 8722 | const clock_id: posix.clockid_t = clockToPosix(clock); | 9082 | const clock_id: posix.clockid_t = clockToPosix(clock); |
| 8723 | var tp: posix.timespec = undefined; | 9083 | var tp: posix.timespec = undefined; |
| 8724 | switch (posix.errno(posix.system.clock_gettime(clock_id, &tp))) { | 9084 | switch (posix.errno(posix.system.clock_gettime(clock_id, &tp))) { |
| ... | @@ -8728,15 +9088,17 @@ fn nowPosix(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp | ... | @@ -8728,15 +9088,17 @@ fn nowPosix(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp |
| 8728 | } | 9088 | } |
| 8729 | } | 9089 | } |
| 8730 | | 9090 | |
| 8731 | const now = switch (native_os) { | 9091 | fn now(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp { |
| 8732 | .windows => nowWindows, | | |
| 8733 | .wasi => nowWasi, | | |
| 8734 | else => nowPosix, | | |
| 8735 | }; | | |
| 8736 | | | |
| 8737 | fn nowWindows(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp { | | |
| 8738 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 9092 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 8739 | _ = t; | 9093 | _ = t; |
| | 9094 | return switch (native_os) { |
| | 9095 | .windows => nowWindows(clock), |
| | 9096 | .wasi => nowWasi(clock), |
| | 9097 | else => nowPosix(clock), |
| | 9098 | }; |
| | 9099 | } |
| | 9100 | |
| | 9101 | fn nowWindows(clock: Io.Clock) Io.Clock.Error!Io.Timestamp { |
| 8740 | switch (clock) { | 9102 | switch (clock) { |
| 8741 | .real => { | 9103 | .real => { |
| 8742 | // RtlGetSystemTimePrecise() has a granularity of 100 nanoseconds | 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,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 { | 9134 | fn nowWasi(clock: Io.Clock) Io.Clock.Error!Io.Timestamp { |
| 8773 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | | |
| 8774 | _ = t; | | |
| 8775 | var ns: std.os.wasi.timestamp_t = undefined; | 9135 | var ns: std.os.wasi.timestamp_t = undefined; |
| 8776 | const err = std.os.wasi.clock_time_get(clockToWasi(clock), 1, &ns); | 9136 | const err = std.os.wasi.clock_time_get(clockToWasi(clock), 1, &ns); |
| 8777 | if (err != .SUCCESS) return error.Unexpected; | 9137 | if (err != .SUCCESS) return error.Unexpected; |
| 8778 | return .fromNanoseconds(ns); | 9138 | return .fromNanoseconds(ns); |
| 8779 | } | 9139 | } |
| 8780 | | 9140 | |
| 8781 | const sleep = switch (native_os) { | 9141 | fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { |
| 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 { | | |
| 8789 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 9142 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 8790 | _ = t; | 9143 | if (use_parking_sleep) return parking_sleep.sleep(timeout); |
| | 9144 | switch (native_os) { |
| | 9145 | .wasi => return sleepWasi(t, timeout), |
| | 9146 | .linux => return sleepLinux(timeout), |
| | 9147 | else => return sleepPosix(t, timeout), |
| | 9148 | } |
| | 9149 | } |
| | 9150 | |
| | 9151 | fn sleepLinux(timeout: Io.Timeout) Io.SleepError!void { |
| 8791 | const clock_id: posix.clockid_t = clockToPosix(switch (timeout) { | 9152 | const clock_id: posix.clockid_t = clockToPosix(switch (timeout) { |
| 8792 | .none => .awake, | 9153 | .none => .awake, |
| 8793 | .duration => |d| d.clock, | 9154 | .duration => |d| d.clock, |
| ... | @@ -8824,21 +9185,7 @@ fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { | ... | @@ -8824,21 +9185,7 @@ fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { |
| 8824 | } | 9185 | } |
| 8825 | } | 9186 | } |
| 8826 | | 9187 | |
| 8827 | fn sleepWindows(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { | 9188 | fn sleepWasi(t: *Threaded, timeout: Io.Timeout) Io.SleepError!void { |
| 8828 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | | |
| 8829 | const t_io = ioBasic(t); | | |
| 8830 | try Thread.checkCancel(); | | |
| 8831 | const ms = ms: { | | |
| 8832 | const d = (try timeout.toDurationFromNow(t_io)) orelse | | |
| 8833 | break :ms std.math.maxInt(windows.DWORD); | | |
| 8834 | break :ms std.math.lossyCast(windows.DWORD, d.raw.toMilliseconds()); | | |
| 8835 | }; | | |
| 8836 | // TODO: alertable true with checkCancel in a loop plus deadline | | |
| 8837 | _ = windows.kernel32.SleepEx(ms, windows.FALSE); | | |
| 8838 | } | | |
| 8839 | | | |
| 8840 | fn sleepWasi(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { | | |
| 8841 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | | |
| 8842 | const t_io = ioBasic(t); | 9189 | const t_io = ioBasic(t); |
| 8843 | const w = std.os.wasi; | 9190 | const w = std.os.wasi; |
| 8844 | | 9191 | |
| ... | @@ -8867,8 +9214,7 @@ fn sleepWasi(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { | ... | @@ -8867,8 +9214,7 @@ fn sleepWasi(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { |
| 8867 | syscall.finish(); | 9214 | syscall.finish(); |
| 8868 | } | 9215 | } |
| 8869 | | 9216 | |
| 8870 | fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { | 9217 | fn sleepPosix(t: *Threaded, timeout: Io.Timeout) Io.SleepError!void { |
| 8871 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | | |
| 8872 | const t_io = ioBasic(t); | 9218 | const t_io = ioBasic(t); |
| 8873 | const sec_type = @typeInfo(posix.timespec).@"struct".fields[0].type; | 9219 | const sec_type = @typeInfo(posix.timespec).@"struct".fields[0].type; |
| 8874 | const nsec_type = @typeInfo(posix.timespec).@"struct".fields[1].type; | 9220 | const nsec_type = @typeInfo(posix.timespec).@"struct".fields[1].type; |
| ... | @@ -9037,93 +9383,90 @@ fn netListenIpWindows( | ... | @@ -9037,93 +9383,90 @@ fn netListenIpWindows( |
| 9037 | var storage: WsaAddress = undefined; | 9383 | var storage: WsaAddress = undefined; |
| 9038 | var addr_len = addressToWsa(&address, &storage); | 9384 | var addr_len = addressToWsa(&address, &storage); |
| 9039 | | 9385 | |
| 9040 | { | 9386 | var syscall: Syscall = try .start(); |
| 9041 | const syscall: Syscall = try .start(); | 9387 | while (true) { |
| 9042 | while (true) { | 9388 | const rc = ws2_32.bind(socket_handle, &storage.any, addr_len); |
| 9043 | const rc = ws2_32.bind(socket_handle, &storage.any, addr_len); | 9389 | if (rc != ws2_32.SOCKET_ERROR) { |
| 9044 | if (rc != ws2_32.SOCKET_ERROR) { | 9390 | syscall.finish(); |
| 9045 | syscall.finish(); | 9391 | break; |
| 9046 | break; | | |
| 9047 | } | | |
| 9048 | switch (ws2_32.WSAGetLastError()) { | | |
| 9049 | .EINTR => { | | |
| 9050 | try syscall.checkCancel(); | | |
| 9051 | continue; | | |
| 9052 | }, | | |
| 9053 | .NOTINITIALISED => { | | |
| 9054 | try initializeWsa(t); | | |
| 9055 | try syscall.checkCancel(); | | |
| 9056 | continue; | | |
| 9057 | }, | | |
| 9058 | else => |e| { | | |
| 9059 | syscall.finish(); | | |
| 9060 | switch (e) { | | |
| 9061 | .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, | | |
| 9062 | .EADDRINUSE => return error.AddressInUse, | | |
| 9063 | .EADDRNOTAVAIL => return error.AddressUnavailable, | | |
| 9064 | .ENOTSOCK => |err| return wsaErrorBug(err), | | |
| 9065 | .EFAULT => |err| return wsaErrorBug(err), | | |
| 9066 | .EINVAL => |err| return wsaErrorBug(err), | | |
| 9067 | .ENOBUFS => return error.SystemResources, | | |
| 9068 | .ENETDOWN => return error.NetworkDown, | | |
| 9069 | else => |err| return windows.unexpectedWSAError(err), | | |
| 9070 | } | | |
| 9071 | }, | | |
| 9072 | } | | |
| 9073 | } | 9392 | } |
| 9074 | } | 9393 | switch (ws2_32.WSAGetLastError()) { |
| 9075 | { | 9394 | .NOTINITIALISED => { |
| 9076 | const syscall: Syscall = try .start(); | | |
| 9077 | while (true) { | | |
| 9078 | const rc = ws2_32.listen(socket_handle, options.kernel_backlog); | | |
| 9079 | if (rc != ws2_32.SOCKET_ERROR) { | | |
| 9080 | syscall.finish(); | 9395 | syscall.finish(); |
| 9081 | break; | 9396 | try initializeWsa(t); |
| 9082 | } | 9397 | syscall = try .start(); |
| 9083 | switch (ws2_32.WSAGetLastError()) { | 9398 | continue; |
| 9084 | .EINTR => { | 9399 | }, |
| 9085 | try syscall.checkCancel(); | 9400 | .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { |
| 9086 | continue; | 9401 | try syscall.checkCancel(); |
| 9087 | }, | 9402 | continue; |
| 9088 | .NOTINITIALISED => { | 9403 | }, |
| 9089 | try initializeWsa(t); | 9404 | else => |e| { |
| 9090 | try syscall.checkCancel(); | 9405 | syscall.finish(); |
| 9091 | continue; | 9406 | switch (e) { |
| 9092 | }, | 9407 | .EADDRINUSE => return error.AddressInUse, |
| 9093 | else => |e| { | 9408 | .EADDRNOTAVAIL => return error.AddressUnavailable, |
| 9094 | syscall.finish(); | 9409 | .ENOTSOCK => |err| return wsaErrorBug(err), |
| 9095 | switch (e) { | 9410 | .EFAULT => |err| return wsaErrorBug(err), |
| 9096 | .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, | 9411 | .EINVAL => |err| return wsaErrorBug(err), |
| 9097 | .ENETDOWN => return error.NetworkDown, | 9412 | .ENOBUFS => return error.SystemResources, |
| 9098 | .EADDRINUSE => return error.AddressInUse, | 9413 | .ENETDOWN => return error.NetworkDown, |
| 9099 | .EISCONN => |err| return wsaErrorBug(err), | 9414 | else => |err| return windows.unexpectedWSAError(err), |
| 9100 | .EINVAL => |err| return wsaErrorBug(err), | 9415 | } |
| 9101 | .EMFILE, .ENOBUFS => return error.SystemResources, | 9416 | }, |
| 9102 | .ENOTSOCK => |err| return wsaErrorBug(err), | | |
| 9103 | .EOPNOTSUPP => |err| return wsaErrorBug(err), | | |
| 9104 | .EINPROGRESS => |err| return wsaErrorBug(err), | | |
| 9105 | else => |err| return windows.unexpectedWSAError(err), | | |
| 9106 | } | | |
| 9107 | }, | | |
| 9108 | } | | |
| 9109 | } | 9417 | } |
| 9110 | } | 9418 | } |
| 9111 | | 9419 | |
| 9112 | try wsaGetSockName(t, socket_handle, &storage.any, &addr_len); | 9420 | syscall = try .start(); |
| 9113 | | 9421 | while (true) { |
| 9114 | return .{ | 9422 | const rc = ws2_32.listen(socket_handle, options.kernel_backlog); |
| 9115 | .socket = .{ | 9423 | if (rc != ws2_32.SOCKET_ERROR) { |
| 9116 | .handle = socket_handle, | 9424 | syscall.finish(); |
| 9117 | .address = addressFromWsa(&storage), | 9425 | break; |
| 9118 | }, | 9426 | } |
| 9119 | }; | 9427 | switch (ws2_32.WSAGetLastError()) { |
| 9120 | } | 9428 | .NOTINITIALISED => { |
| 9121 | | 9429 | syscall.finish(); |
| 9122 | fn netListenIpUnavailable( | 9430 | try initializeWsa(t); |
| 9123 | userdata: ?*anyopaque, | 9431 | syscall = try .start(); |
| 9124 | address: IpAddress, | 9432 | continue; |
| 9125 | options: IpAddress.ListenOptions, | 9433 | }, |
| 9126 | ) IpAddress.ListenError!net.Server { | 9434 | .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { |
| | 9435 | try syscall.checkCancel(); |
| | 9436 | continue; |
| | 9437 | }, |
| | 9438 | else => |e| { |
| | 9439 | syscall.finish(); |
| | 9440 | switch (e) { |
| | 9441 | .ENETDOWN => return error.NetworkDown, |
| | 9442 | .EADDRINUSE => return error.AddressInUse, |
| | 9443 | .EISCONN => |err| return wsaErrorBug(err), |
| | 9444 | .EINVAL => |err| return wsaErrorBug(err), |
| | 9445 | .EMFILE, .ENOBUFS => return error.SystemResources, |
| | 9446 | .ENOTSOCK => |err| return wsaErrorBug(err), |
| | 9447 | .EOPNOTSUPP => |err| return wsaErrorBug(err), |
| | 9448 | .EINPROGRESS => |err| return wsaErrorBug(err), |
| | 9449 | else => |err| return windows.unexpectedWSAError(err), |
| | 9450 | } |
| | 9451 | }, |
| | 9452 | } |
| | 9453 | } |
| | 9454 | |
| | 9455 | try wsaGetSockName(t, socket_handle, &storage.any, &addr_len); |
| | 9456 | |
| | 9457 | return .{ |
| | 9458 | .socket = .{ |
| | 9459 | .handle = socket_handle, |
| | 9460 | .address = addressFromWsa(&storage), |
| | 9461 | }, |
| | 9462 | }; |
| | 9463 | } |
| | 9464 | |
| | 9465 | fn netListenIpUnavailable( |
| | 9466 | userdata: ?*anyopaque, |
| | 9467 | address: IpAddress, |
| | 9468 | options: IpAddress.ListenOptions, |
| | 9469 | ) IpAddress.ListenError!net.Server { |
| 9127 | _ = userdata; | 9470 | _ = userdata; |
| 9128 | _ = address; | 9471 | _ = address; |
| 9129 | _ = options; | 9472 | _ = options; |
| ... | @@ -9193,24 +9536,24 @@ fn netListenUnixWindows( | ... | @@ -9193,24 +9536,24 @@ fn netListenUnixWindows( |
| 9193 | var storage: WsaAddress = undefined; | 9536 | var storage: WsaAddress = undefined; |
| 9194 | const addr_len = addressUnixToWsa(address, &storage); | 9537 | const addr_len = addressUnixToWsa(address, &storage); |
| 9195 | | 9538 | |
| 9196 | const syscall: Syscall = try .start(); | 9539 | var syscall: Syscall = try .start(); |
| 9197 | while (true) { | 9540 | while (true) { |
| 9198 | const rc = ws2_32.bind(socket_handle, &storage.any, addr_len); | 9541 | const rc = ws2_32.bind(socket_handle, &storage.any, addr_len); |
| 9199 | if (rc != ws2_32.SOCKET_ERROR) break; | 9542 | if (rc != ws2_32.SOCKET_ERROR) break; |
| 9200 | switch (ws2_32.WSAGetLastError()) { | 9543 | switch (ws2_32.WSAGetLastError()) { |
| 9201 | .EINTR => { | | |
| 9202 | try syscall.checkCancel(); | | |
| 9203 | continue; | | |
| 9204 | }, | | |
| 9205 | .NOTINITIALISED => { | 9544 | .NOTINITIALISED => { |
| | 9545 | syscall.finish(); |
| 9206 | try initializeWsa(t); | 9546 | try initializeWsa(t); |
| | 9547 | syscall = try .start(); |
| | 9548 | continue; |
| | 9549 | }, |
| | 9550 | .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { |
| 9207 | try syscall.checkCancel(); | 9551 | try syscall.checkCancel(); |
| 9208 | continue; | 9552 | continue; |
| 9209 | }, | 9553 | }, |
| 9210 | else => |e| { | 9554 | else => |e| { |
| 9211 | syscall.finish(); | 9555 | syscall.finish(); |
| 9212 | switch (e) { | 9556 | switch (e) { |
| 9213 | .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, | | |
| 9214 | .EADDRINUSE => return error.AddressInUse, | 9557 | .EADDRINUSE => return error.AddressInUse, |
| 9215 | .EADDRNOTAVAIL => return error.AddressUnavailable, | 9558 | .EADDRNOTAVAIL => return error.AddressUnavailable, |
| 9216 | .ENOTSOCK => |err| return wsaErrorBug(err), | 9559 | .ENOTSOCK => |err| return wsaErrorBug(err), |
| ... | @@ -9232,15 +9575,16 @@ fn netListenUnixWindows( | ... | @@ -9232,15 +9575,16 @@ fn netListenUnixWindows( |
| 9232 | return socket_handle; | 9575 | return socket_handle; |
| 9233 | } | 9576 | } |
| 9234 | switch (ws2_32.WSAGetLastError()) { | 9577 | switch (ws2_32.WSAGetLastError()) { |
| 9235 | .EINTR => continue, | 9578 | .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => continue, |
| 9236 | .NOTINITIALISED => { | 9579 | .NOTINITIALISED => { |
| | 9580 | syscall.finish(); |
| 9237 | try initializeWsa(t); | 9581 | try initializeWsa(t); |
| | 9582 | syscall = try .start(); |
| 9238 | continue; | 9583 | continue; |
| 9239 | }, | 9584 | }, |
| 9240 | else => |e| { | 9585 | else => |e| { |
| 9241 | syscall.finish(); | 9586 | syscall.finish(); |
| 9242 | switch (e) { | 9587 | switch (e) { |
| 9243 | .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, | | |
| 9244 | .ENETDOWN => return error.NetworkDown, | 9588 | .ENETDOWN => return error.NetworkDown, |
| 9245 | .EADDRINUSE => return error.AddressInUse, | 9589 | .EADDRINUSE => return error.AddressInUse, |
| 9246 | .EISCONN => |err| return wsaErrorBug(err), | 9590 | .EISCONN => |err| return wsaErrorBug(err), |
| ... | @@ -9469,7 +9813,7 @@ fn wsaGetSockName( | ... | @@ -9469,7 +9813,7 @@ fn wsaGetSockName( |
| 9469 | addr: *ws2_32.sockaddr, | 9813 | addr: *ws2_32.sockaddr, |
| 9470 | addr_len: *i32, | 9814 | addr_len: *i32, |
| 9471 | ) !void { | 9815 | ) !void { |
| 9472 | const syscall: Syscall = try .start(); | 9816 | var syscall: Syscall = try .start(); |
| 9473 | while (true) { | 9817 | while (true) { |
| 9474 | const rc = ws2_32.getsockname(handle, addr, addr_len); | 9818 | const rc = ws2_32.getsockname(handle, addr, addr_len); |
| 9475 | if (rc != ws2_32.SOCKET_ERROR) { | 9819 | if (rc != ws2_32.SOCKET_ERROR) { |
| ... | @@ -9477,19 +9821,19 @@ fn wsaGetSockName( | ... | @@ -9477,19 +9821,19 @@ fn wsaGetSockName( |
| 9477 | return; | 9821 | return; |
| 9478 | } | 9822 | } |
| 9479 | switch (ws2_32.WSAGetLastError()) { | 9823 | switch (ws2_32.WSAGetLastError()) { |
| 9480 | .EINTR => { | 9824 | .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { |
| 9481 | try syscall.checkCancel(); | 9825 | try syscall.checkCancel(); |
| 9482 | continue; | 9826 | continue; |
| 9483 | }, | 9827 | }, |
| 9484 | .NOTINITIALISED => { | 9828 | .NOTINITIALISED => { |
| | 9829 | syscall.finish(); |
| 9485 | try initializeWsa(t); | 9830 | try initializeWsa(t); |
| 9486 | try syscall.checkCancel(); | 9831 | syscall = try .start(); |
| 9487 | continue; | 9832 | continue; |
| 9488 | }, | 9833 | }, |
| 9489 | else => |e| { | 9834 | else => |e| { |
| 9490 | syscall.finish(); | 9835 | syscall.finish(); |
| 9491 | switch (e) { | 9836 | switch (e) { |
| 9492 | .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, | | |
| 9493 | .ENETDOWN => return error.NetworkDown, | 9837 | .ENETDOWN => return error.NetworkDown, |
| 9494 | .EFAULT => |err| return wsaErrorBug(err), | 9838 | .EFAULT => |err| return wsaErrorBug(err), |
| 9495 | .ENOTSOCK => |err| return wsaErrorBug(err), | 9839 | .ENOTSOCK => |err| return wsaErrorBug(err), |
| ... | @@ -9530,21 +9874,30 @@ fn setSocketOption(fd: posix.fd_t, level: i32, opt_name: u32, option: u32) !void | ... | @@ -9530,21 +9874,30 @@ fn setSocketOption(fd: posix.fd_t, level: i32, opt_name: u32, option: u32) !void |
| 9530 | | 9874 | |
| 9531 | fn setSocketOptionWsa(t: *Threaded, socket: Io.net.Socket.Handle, level: i32, opt_name: u32, option: u32) !void { | 9875 | fn setSocketOptionWsa(t: *Threaded, socket: Io.net.Socket.Handle, level: i32, opt_name: u32, option: u32) !void { |
| 9532 | const o: []const u8 = @ptrCast(&option); | 9876 | const o: []const u8 = @ptrCast(&option); |
| | 9877 | var syscall: Syscall = try .start(); |
| 9533 | const rc = ws2_32.setsockopt(socket, level, @bitCast(opt_name), o.ptr, @intCast(o.len)); | 9878 | const rc = ws2_32.setsockopt(socket, level, @bitCast(opt_name), o.ptr, @intCast(o.len)); |
| 9534 | while (true) { | 9879 | while (true) { |
| 9535 | if (rc != ws2_32.SOCKET_ERROR) return; | 9880 | if (rc != ws2_32.SOCKET_ERROR) return syscall.finish(); |
| 9536 | switch (ws2_32.WSAGetLastError()) { | 9881 | switch (ws2_32.WSAGetLastError()) { |
| 9537 | .EINTR => continue, | 9882 | .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { |
| 9538 | .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, | 9883 | try syscall.checkCancel(); |
| | 9884 | continue; |
| | 9885 | }, |
| 9539 | .NOTINITIALISED => { | 9886 | .NOTINITIALISED => { |
| | 9887 | syscall.finish(); |
| 9540 | try initializeWsa(t); | 9888 | try initializeWsa(t); |
| | 9889 | syscall = try .start(); |
| 9541 | continue; | 9890 | continue; |
| 9542 | }, | 9891 | }, |
| 9543 | .ENETDOWN => return error.NetworkDown, | 9892 | .ENETDOWN => return syscall.fail(error.NetworkDown), |
| 9544 | .EFAULT => |err| return wsaErrorBug(err), | 9893 | .EFAULT, .ENOTSOCK, .EINVAL => |err| { |
| 9545 | .ENOTSOCK => |err| return wsaErrorBug(err), | 9894 | syscall.finish(); |
| 9546 | .EINVAL => |err| return wsaErrorBug(err), | 9895 | return wsaErrorBug(err); |
| 9547 | else => |err| return windows.unexpectedWSAError(err), | 9896 | }, |
| | 9897 | else => |err| { |
| | 9898 | syscall.finish(); |
| | 9899 | return windows.unexpectedWSAError(err); |
| | 9900 | }, |
| 9548 | } | 9901 | } |
| 9549 | } | 9902 | } |
| 9550 | } | 9903 | } |
| ... | @@ -9592,7 +9945,7 @@ fn netConnectIpWindows( | ... | @@ -9592,7 +9945,7 @@ fn netConnectIpWindows( |
| 9592 | var storage: WsaAddress = undefined; | 9945 | var storage: WsaAddress = undefined; |
| 9593 | var addr_len = addressToWsa(address, &storage); | 9946 | var addr_len = addressToWsa(address, &storage); |
| 9594 | | 9947 | |
| 9595 | const syscall: Syscall = try .start(); | 9948 | var syscall: Syscall = try .start(); |
| 9596 | while (true) { | 9949 | while (true) { |
| 9597 | const rc = ws2_32.connect(socket_handle, &storage.any, addr_len); | 9950 | const rc = ws2_32.connect(socket_handle, &storage.any, addr_len); |
| 9598 | if (rc != ws2_32.SOCKET_ERROR) { | 9951 | if (rc != ws2_32.SOCKET_ERROR) { |
| ... | @@ -9600,19 +9953,19 @@ fn netConnectIpWindows( | ... | @@ -9600,19 +9953,19 @@ fn netConnectIpWindows( |
| 9600 | break; | 9953 | break; |
| 9601 | } | 9954 | } |
| 9602 | switch (ws2_32.WSAGetLastError()) { | 9955 | switch (ws2_32.WSAGetLastError()) { |
| 9603 | .EINTR => { | 9956 | .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { |
| 9604 | try syscall.checkCancel(); | 9957 | try syscall.checkCancel(); |
| 9605 | continue; | 9958 | continue; |
| 9606 | }, | 9959 | }, |
| 9607 | .NOTINITIALISED => { | 9960 | .NOTINITIALISED => { |
| | 9961 | syscall.finish(); |
| 9608 | try initializeWsa(t); | 9962 | try initializeWsa(t); |
| 9609 | try syscall.checkCancel(); | 9963 | syscall = try .start(); |
| 9610 | continue; | 9964 | continue; |
| 9611 | }, | 9965 | }, |
| 9612 | else => |e| { | 9966 | else => |e| { |
| 9613 | syscall.finish(); | 9967 | syscall.finish(); |
| 9614 | switch (e) { | 9968 | switch (e) { |
| 9615 | .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, | | |
| 9616 | .EADDRNOTAVAIL => return error.AddressUnavailable, | 9969 | .EADDRNOTAVAIL => return error.AddressUnavailable, |
| 9617 | .ECONNREFUSED => return error.ConnectionRefused, | 9970 | .ECONNREFUSED => return error.ConnectionRefused, |
| 9618 | .ECONNRESET => return error.ConnectionResetByPeer, | 9971 | .ECONNRESET => return error.ConnectionResetByPeer, |
| ... | @@ -9682,27 +10035,36 @@ fn netConnectUnixWindows( | ... | @@ -9682,27 +10035,36 @@ fn netConnectUnixWindows( |
| 9682 | var storage: WsaAddress = undefined; | 10035 | var storage: WsaAddress = undefined; |
| 9683 | const addr_len = addressUnixToWsa(address, &storage); | 10036 | const addr_len = addressUnixToWsa(address, &storage); |
| 9684 | | 10037 | |
| | 10038 | var syscall: Syscall = try .start(); |
| 9685 | while (true) { | 10039 | while (true) { |
| 9686 | const rc = ws2_32.connect(socket_handle, &storage.any, addr_len); | 10040 | const rc = ws2_32.connect(socket_handle, &storage.any, addr_len); |
| 9687 | if (rc != ws2_32.SOCKET_ERROR) break; | 10041 | if (rc != ws2_32.SOCKET_ERROR) break; |
| 9688 | switch (ws2_32.WSAGetLastError()) { | 10042 | switch (ws2_32.WSAGetLastError()) { |
| 9689 | .EINTR => continue, | 10043 | .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { |
| 9690 | .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, | 10044 | try syscall.checkCancel(); |
| | 10045 | continue; |
| | 10046 | }, |
| 9691 | .NOTINITIALISED => { | 10047 | .NOTINITIALISED => { |
| | 10048 | syscall.finish(); |
| 9692 | try initializeWsa(t); | 10049 | try initializeWsa(t); |
| | 10050 | syscall = try .start(); |
| 9693 | continue; | 10051 | continue; |
| 9694 | }, | 10052 | }, |
| 9695 | | 10053 | else => |e| { |
| 9696 | .ECONNREFUSED => return error.FileNotFound, | 10054 | syscall.finish(); |
| 9697 | .EFAULT => |err| return wsaErrorBug(err), | 10055 | switch (e) { |
| 9698 | .EINVAL => |err| return wsaErrorBug(err), | 10056 | .ECONNREFUSED => return error.FileNotFound, |
| 9699 | .EISCONN => |err| return wsaErrorBug(err), | 10057 | .EFAULT => |err| return wsaErrorBug(err), |
| 9700 | .ENOTSOCK => |err| return wsaErrorBug(err), | 10058 | .EINVAL => |err| return wsaErrorBug(err), |
| 9701 | .EWOULDBLOCK => return error.WouldBlock, | 10059 | .EISCONN => |err| return wsaErrorBug(err), |
| 9702 | .EACCES => return error.AccessDenied, | 10060 | .ENOTSOCK => |err| return wsaErrorBug(err), |
| 9703 | .ENOBUFS => return error.SystemResources, | 10061 | .EWOULDBLOCK => return error.WouldBlock, |
| 9704 | .EAFNOSUPPORT => return error.AddressFamilyUnsupported, | 10062 | .EACCES => return error.AccessDenied, |
| 9705 | else => |err| return windows.unexpectedWSAError(err), | 10063 | .ENOBUFS => return error.SystemResources, |
| | 10064 | .EAFNOSUPPORT => return error.AddressFamilyUnsupported, |
| | 10065 | else => |err| return windows.unexpectedWSAError(err), |
| | 10066 | } |
| | 10067 | }, |
| 9706 | } | 10068 | } |
| 9707 | } | 10069 | } |
| 9708 | | 10070 | |
| ... | @@ -9756,7 +10118,7 @@ fn netBindIpWindows( | ... | @@ -9756,7 +10118,7 @@ fn netBindIpWindows( |
| 9756 | var storage: WsaAddress = undefined; | 10118 | var storage: WsaAddress = undefined; |
| 9757 | var addr_len = addressToWsa(address, &storage); | 10119 | var addr_len = addressToWsa(address, &storage); |
| 9758 | | 10120 | |
| 9759 | const syscall: Syscall = try .start(); | 10121 | var syscall: Syscall = try .start(); |
| 9760 | while (true) { | 10122 | while (true) { |
| 9761 | const rc = ws2_32.bind(socket_handle, &storage.any, addr_len); | 10123 | const rc = ws2_32.bind(socket_handle, &storage.any, addr_len); |
| 9762 | if (rc != ws2_32.SOCKET_ERROR) { | 10124 | if (rc != ws2_32.SOCKET_ERROR) { |
| ... | @@ -9764,19 +10126,19 @@ fn netBindIpWindows( | ... | @@ -9764,19 +10126,19 @@ fn netBindIpWindows( |
| 9764 | break; | 10126 | break; |
| 9765 | } | 10127 | } |
| 9766 | switch (ws2_32.WSAGetLastError()) { | 10128 | switch (ws2_32.WSAGetLastError()) { |
| 9767 | .EINTR => { | 10129 | .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { |
| 9768 | try syscall.checkCancel(); | 10130 | try syscall.checkCancel(); |
| 9769 | continue; | 10131 | continue; |
| 9770 | }, | 10132 | }, |
| 9771 | .NOTINITIALISED => { | 10133 | .NOTINITIALISED => { |
| | 10134 | syscall.finish(); |
| 9772 | try initializeWsa(t); | 10135 | try initializeWsa(t); |
| 9773 | try syscall.checkCancel(); | 10136 | syscall = try .start(); |
| 9774 | continue; | 10137 | continue; |
| 9775 | }, | 10138 | }, |
| 9776 | else => |e| { | 10139 | else => |e| { |
| 9777 | syscall.finish(); | 10140 | syscall.finish(); |
| 9778 | switch (e) { | 10141 | switch (e) { |
| 9779 | .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, | | |
| 9780 | .EADDRINUSE => return error.AddressInUse, | 10142 | .EADDRINUSE => return error.AddressInUse, |
| 9781 | .EADDRNOTAVAIL => return error.AddressUnavailable, | 10143 | .EADDRNOTAVAIL => return error.AddressUnavailable, |
| 9782 | .ENOTSOCK => |err| return wsaErrorBug(err), | 10144 | .ENOTSOCK => |err| return wsaErrorBug(err), |
| ... | @@ -9886,7 +10248,7 @@ fn openSocketWsa( | ... | @@ -9886,7 +10248,7 @@ fn openSocketWsa( |
| 9886 | const mode = posixSocketMode(options.mode); | 10248 | const mode = posixSocketMode(options.mode); |
| 9887 | const protocol = posixProtocol(options.protocol); | 10249 | const protocol = posixProtocol(options.protocol); |
| 9888 | const flags: u32 = ws2_32.WSA_FLAG_OVERLAPPED | ws2_32.WSA_FLAG_NO_HANDLE_INHERIT; | 10250 | const flags: u32 = ws2_32.WSA_FLAG_OVERLAPPED | ws2_32.WSA_FLAG_NO_HANDLE_INHERIT; |
| 9889 | const syscall: Syscall = try .start(); | 10251 | var syscall: Syscall = try .start(); |
| 9890 | while (true) { | 10252 | while (true) { |
| 9891 | const rc = ws2_32.WSASocketW(family, @bitCast(mode), @bitCast(protocol), null, 0, flags); | 10253 | const rc = ws2_32.WSASocketW(family, @bitCast(mode), @bitCast(protocol), null, 0, flags); |
| 9892 | if (rc != ws2_32.INVALID_SOCKET) { | 10254 | if (rc != ws2_32.INVALID_SOCKET) { |
| ... | @@ -9894,19 +10256,19 @@ fn openSocketWsa( | ... | @@ -9894,19 +10256,19 @@ fn openSocketWsa( |
| 9894 | return rc; | 10256 | return rc; |
| 9895 | } | 10257 | } |
| 9896 | switch (ws2_32.WSAGetLastError()) { | 10258 | switch (ws2_32.WSAGetLastError()) { |
| 9897 | .EINTR => { | 10259 | .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { |
| 9898 | try syscall.checkCancel(); | 10260 | try syscall.checkCancel(); |
| 9899 | continue; | 10261 | continue; |
| 9900 | }, | 10262 | }, |
| 9901 | .NOTINITIALISED => { | 10263 | .NOTINITIALISED => { |
| | 10264 | syscall.finish(); |
| 9902 | try initializeWsa(t); | 10265 | try initializeWsa(t); |
| 9903 | try syscall.checkCancel(); | 10266 | syscall = try .start(); |
| 9904 | continue; | 10267 | continue; |
| 9905 | }, | 10268 | }, |
| 9906 | else => |e| { | 10269 | else => |e| { |
| 9907 | syscall.finish(); | 10270 | syscall.finish(); |
| 9908 | switch (e) { | 10271 | switch (e) { |
| 9909 | .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, | | |
| 9910 | .EAFNOSUPPORT => return error.AddressFamilyUnsupported, | 10272 | .EAFNOSUPPORT => return error.AddressFamilyUnsupported, |
| 9911 | .EMFILE => return error.ProcessFdQuotaExceeded, | 10273 | .EMFILE => return error.ProcessFdQuotaExceeded, |
| 9912 | .ENOBUFS => return error.SystemResources, | 10274 | .ENOBUFS => return error.SystemResources, |
| ... | @@ -9984,7 +10346,7 @@ fn netAcceptWindows(userdata: ?*anyopaque, listen_handle: net.Socket.Handle) net | ... | @@ -9984,7 +10346,7 @@ fn netAcceptWindows(userdata: ?*anyopaque, listen_handle: net.Socket.Handle) net |
| 9984 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 10346 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 9985 | var storage: WsaAddress = undefined; | 10347 | var storage: WsaAddress = undefined; |
| 9986 | var addr_len: i32 = @sizeOf(WsaAddress); | 10348 | var addr_len: i32 = @sizeOf(WsaAddress); |
| 9987 | const syscall: Syscall = try .start(); | 10349 | var syscall: Syscall = try .start(); |
| 9988 | while (true) { | 10350 | while (true) { |
| 9989 | const rc = ws2_32.accept(listen_handle, &storage.any, &addr_len); | 10351 | const rc = ws2_32.accept(listen_handle, &storage.any, &addr_len); |
| 9990 | if (rc != ws2_32.INVALID_SOCKET) { | 10352 | if (rc != ws2_32.INVALID_SOCKET) { |
| ... | @@ -9995,19 +10357,19 @@ fn netAcceptWindows(userdata: ?*anyopaque, listen_handle: net.Socket.Handle) net | ... | @@ -9995,19 +10357,19 @@ fn netAcceptWindows(userdata: ?*anyopaque, listen_handle: net.Socket.Handle) net |
| 9995 | } }; | 10357 | } }; |
| 9996 | } | 10358 | } |
| 9997 | switch (ws2_32.WSAGetLastError()) { | 10359 | switch (ws2_32.WSAGetLastError()) { |
| 9998 | .EINTR => { | 10360 | .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { |
| 9999 | try syscall.checkCancel(); | 10361 | try syscall.checkCancel(); |
| 10000 | continue; | 10362 | continue; |
| 10001 | }, | 10363 | }, |
| 10002 | .NOTINITIALISED => { | 10364 | .NOTINITIALISED => { |
| | 10365 | syscall.finish(); |
| 10003 | try initializeWsa(t); | 10366 | try initializeWsa(t); |
| 10004 | try syscall.checkCancel(); | 10367 | syscall = try .start(); |
| 10005 | continue; | 10368 | continue; |
| 10006 | }, | 10369 | }, |
| 10007 | else => |e| { | 10370 | else => |e| { |
| 10008 | syscall.finish(); | 10371 | syscall.finish(); |
| 10009 | switch (e) { | 10372 | switch (e) { |
| 10010 | .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, | | |
| 10011 | .ECONNRESET => return error.ConnectionAborted, | 10373 | .ECONNRESET => return error.ConnectionAborted, |
| 10012 | .EFAULT => |err| return wsaErrorBug(err), | 10374 | .EFAULT => |err| return wsaErrorBug(err), |
| 10013 | .ENOTSOCK => |err| return wsaErrorBug(err), | 10375 | .ENOTSOCK => |err| return wsaErrorBug(err), |
| ... | @@ -10141,48 +10503,41 @@ fn netReadWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, data: [][]u8 | ... | @@ -10141,48 +10503,41 @@ fn netReadWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, data: [][]u8 |
| 10141 | break :b bufs; | 10503 | break :b bufs; |
| 10142 | }; | 10504 | }; |
| 10143 | | 10505 | |
| | 10506 | var syscall: Syscall = try .start(); |
| 10144 | while (true) { | 10507 | while (true) { |
| 10145 | try Thread.checkCancel(); | | |
| 10146 | | | |
| 10147 | var flags: u32 = 0; | 10508 | var flags: u32 = 0; |
| 10148 | var overlapped: windows.OVERLAPPED = std.mem.zeroes(windows.OVERLAPPED); | | |
| 10149 | var n: u32 = undefined; | 10509 | var n: u32 = undefined; |
| 10150 | const rc = ws2_32.WSARecv(handle, bufs.ptr, @intCast(bufs.len), &n, &flags, &overlapped, null); | 10510 | const rc = ws2_32.WSARecv(handle, bufs.ptr, @intCast(bufs.len), &n, &flags, null, null); |
| 10151 | if (rc != ws2_32.SOCKET_ERROR) return n; | 10511 | if (rc != ws2_32.SOCKET_ERROR) { |
| 10152 | const wsa_error: ws2_32.WinsockError = switch (ws2_32.WSAGetLastError()) { | 10512 | syscall.finish(); |
| 10153 | .IO_PENDING => e: { | 10513 | return n; |
| 10154 | var result_flags: u32 = undefined; | 10514 | } |
| 10155 | const overlapped_rc = ws2_32.WSAGetOverlappedResult( | 10515 | switch (ws2_32.WSAGetLastError()) { |
| 10156 | handle, | 10516 | .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { |
| 10157 | &overlapped, | 10517 | try syscall.checkCancel(); |
| 10158 | &n, | 10518 | continue; |
| 10159 | windows.TRUE, | | |
| 10160 | &result_flags, | | |
| 10161 | ); | | |
| 10162 | if (overlapped_rc == windows.FALSE) { | | |
| 10163 | break :e ws2_32.WSAGetLastError(); | | |
| 10164 | } else { | | |
| 10165 | return n; | | |
| 10166 | } | | |
| 10167 | }, | 10519 | }, |
| 10168 | else => |err| err, | | |
| 10169 | }; | | |
| 10170 | switch (wsa_error) { | | |
| 10171 | .EINTR => continue, | | |
| 10172 | .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, | | |
| 10173 | .NOTINITIALISED => { | 10520 | .NOTINITIALISED => { |
| | 10521 | syscall.finish(); |
| 10174 | try initializeWsa(t); | 10522 | try initializeWsa(t); |
| | 10523 | syscall = try .start(); |
| 10175 | continue; | 10524 | continue; |
| 10176 | }, | 10525 | }, |
| 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 | .EFAULT => unreachable, // a pointer is not completely contained in user address space. | 10531 | .EFAULT => unreachable, // a pointer is not completely contained in user address space. |
| 10180 | .EINVAL => |err| return wsaErrorBug(err), | 10532 | |
| 10181 | .EMSGSIZE => |err| return wsaErrorBug(err), | 10533 | else => |err| { |
| 10182 | .ENETDOWN => return error.NetworkDown, | 10534 | syscall.finish(); |
| 10183 | .ENETRESET => return error.ConnectionResetByPeer, | 10535 | switch (err) { |
| 10184 | .ENOTCONN => return error.SocketUnconnected, | 10536 | .EINVAL => return wsaErrorBug(err), |
| 10185 | else => |err| return windows.unexpectedWSAError(err), | 10537 | .EMSGSIZE => return wsaErrorBug(err), |
| | 10538 | else => return windows.unexpectedWSAError(err), |
| | 10539 | } |
| | 10540 | }, |
| 10186 | } | 10541 | } |
| 10187 | } | 10542 | } |
| 10188 | } | 10543 | } |
| ... | @@ -10269,7 +10624,7 @@ fn netSendOne( | ... | @@ -10269,7 +10624,7 @@ fn netSendOne( |
| 10269 | .controllen = @intCast(message.control.len), | 10624 | .controllen = @intCast(message.control.len), |
| 10270 | .flags = 0, | 10625 | .flags = 0, |
| 10271 | }; | 10626 | }; |
| 10272 | const syscall: Syscall = try .start(); | 10627 | var syscall: Syscall = try .start(); |
| 10273 | while (true) { | 10628 | while (true) { |
| 10274 | const rc = posix.system.sendmsg(handle, &msg, flags); | 10629 | const rc = posix.system.sendmsg(handle, &msg, flags); |
| 10275 | if (is_windows) { | 10630 | if (is_windows) { |
| ... | @@ -10279,19 +10634,19 @@ fn netSendOne( | ... | @@ -10279,19 +10634,19 @@ fn netSendOne( |
| 10279 | return; | 10634 | return; |
| 10280 | } | 10635 | } |
| 10281 | switch (ws2_32.WSAGetLastError()) { | 10636 | switch (ws2_32.WSAGetLastError()) { |
| 10282 | .EINTR => { | 10637 | .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { |
| 10283 | try syscall.checkCancel(); | 10638 | try syscall.checkCancel(); |
| 10284 | continue; | 10639 | continue; |
| 10285 | }, | 10640 | }, |
| 10286 | .NOTINITIALISED => { | 10641 | .NOTINITIALISED => { |
| | 10642 | syscall.finish(); |
| 10287 | try initializeWsa(t); | 10643 | try initializeWsa(t); |
| 10288 | try syscall.checkCancel(); | 10644 | syscall = try .start(); |
| 10289 | continue; | 10645 | continue; |
| 10290 | }, | 10646 | }, |
| 10291 | else => |e| { | 10647 | else => |e| { |
| 10292 | syscall.finish(); | 10648 | syscall.finish(); |
| 10293 | switch (e) { | 10649 | switch (e) { |
| 10294 | .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, | | |
| 10295 | .EACCES => return error.AccessDenied, | 10650 | .EACCES => return error.AccessDenied, |
| 10296 | .EADDRNOTAVAIL => return error.AddressUnavailable, | 10651 | .EADDRNOTAVAIL => return error.AddressUnavailable, |
| 10297 | .ECONNRESET => return error.ConnectionResetByPeer, | 10652 | .ECONNRESET => return error.ConnectionResetByPeer, |
| ... | @@ -10729,49 +11084,44 @@ fn netWriteWindows( | ... | @@ -10729,49 +11084,44 @@ fn netWriteWindows( |
| 10729 | }, | 11084 | }, |
| 10730 | }; | 11085 | }; |
| 10731 | | 11086 | |
| | 11087 | var syscall: Syscall = try .start(); |
| 10732 | while (true) { | 11088 | while (true) { |
| 10733 | try Thread.checkCancel(); | | |
| 10734 | | | |
| 10735 | var n: u32 = undefined; | 11089 | var n: u32 = undefined; |
| 10736 | var overlapped: windows.OVERLAPPED = std.mem.zeroes(windows.OVERLAPPED); | 11090 | const rc = ws2_32.WSASend(handle, &iovecs, len, &n, 0, null, null); |
| 10737 | const rc = ws2_32.WSASend(handle, &iovecs, len, &n, 0, &overlapped, null); | 11091 | if (rc != ws2_32.SOCKET_ERROR) { |
| 10738 | if (rc != ws2_32.SOCKET_ERROR) return n; | 11092 | syscall.finish(); |
| 10739 | const wsa_error: ws2_32.WinsockError = switch (ws2_32.WSAGetLastError()) { | 11093 | return n; |
| 10740 | .IO_PENDING => e: { | 11094 | } |
| 10741 | var result_flags: u32 = undefined; | 11095 | switch (ws2_32.WSAGetLastError()) { |
| 10742 | const overlapped_rc = ws2_32.WSAGetOverlappedResult( | 11096 | .IO_PENDING => unreachable, // not overlapped |
| 10743 | handle, | 11097 | .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { |
| 10744 | &overlapped, | 11098 | try syscall.checkCancel(); |
| 10745 | &n, | 11099 | continue; |
| 10746 | windows.TRUE, | | |
| 10747 | &result_flags, | | |
| 10748 | ); | | |
| 10749 | if (overlapped_rc == windows.FALSE) { | | |
| 10750 | break :e ws2_32.WSAGetLastError(); | | |
| 10751 | } else { | | |
| 10752 | return n; | | |
| 10753 | } | | |
| 10754 | }, | 11100 | }, |
| 10755 | else => |err| err, | | |
| 10756 | }; | | |
| 10757 | switch (wsa_error) { | | |
| 10758 | .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => continue, | | |
| 10759 | .NOTINITIALISED => { | 11101 | .NOTINITIALISED => { |
| | 11102 | syscall.finish(); |
| 10760 | try initializeWsa(t); | 11103 | try initializeWsa(t); |
| | 11104 | syscall = try .start(); |
| 10761 | continue; | 11105 | continue; |
| 10762 | }, | 11106 | }, |
| 10763 | | 11107 | |
| 10764 | .ECONNABORTED => return error.ConnectionResetByPeer, | 11108 | .ECONNABORTED => return syscall.fail(error.ConnectionResetByPeer), |
| 10765 | .ECONNRESET => return error.ConnectionResetByPeer, | 11109 | .ECONNRESET => return syscall.fail(error.ConnectionResetByPeer), |
| 10766 | .EINVAL => return error.SocketUnconnected, | 11110 | .EINVAL => return syscall.fail(error.SocketUnconnected), |
| 10767 | .ENETDOWN => return error.NetworkDown, | 11111 | .ENETDOWN => return syscall.fail(error.NetworkDown), |
| 10768 | .ENETRESET => return error.ConnectionResetByPeer, | 11112 | .ENETRESET => return syscall.fail(error.ConnectionResetByPeer), |
| 10769 | .ENOBUFS => return error.SystemResources, | 11113 | .ENOBUFS => return syscall.fail(error.SystemResources), |
| 10770 | .ENOTCONN => return error.SocketUnconnected, | 11114 | .ENOTCONN => return syscall.fail(error.SocketUnconnected), |
| 10771 | .ENOTSOCK => |err| return wsaErrorBug(err), | 11115 | |
| 10772 | .EOPNOTSUPP => |err| return wsaErrorBug(err), | 11116 | else => |err| { |
| 10773 | .ESHUTDOWN => |err| return wsaErrorBug(err), | 11117 | syscall.finish(); |
| 10774 | else => |err| return windows.unexpectedWSAError(err), | 11118 | switch (err) { |
| | 11119 | .ENOTSOCK => return wsaErrorBug(err), |
| | 11120 | .EOPNOTSUPP => return wsaErrorBug(err), |
| | 11121 | .ESHUTDOWN => return wsaErrorBug(err), |
| | 11122 | else => return windows.unexpectedWSAError(err), |
| | 11123 | } |
| | 11124 | }, |
| 10775 | } | 11125 | } |
| 10776 | } | 11126 | } |
| 10777 | } | 11127 | } |
| ... | @@ -10872,7 +11222,6 @@ fn netShutdownPosix(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.S | ... | @@ -10872,7 +11222,6 @@ fn netShutdownPosix(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.S |
| 10872 | fn netShutdownWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.ShutdownHow) net.ShutdownError!void { | 11222 | fn netShutdownWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.ShutdownHow) net.ShutdownError!void { |
| 10873 | if (!have_networking) return error.NetworkDown; | 11223 | if (!have_networking) return error.NetworkDown; |
| 10874 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 11224 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 10875 | const current_thread = Thread.getCurrent(t); | | |
| 10876 | | 11225 | |
| 10877 | const wsa_how: i32 = switch (how) { | 11226 | const wsa_how: i32 = switch (how) { |
| 10878 | .recv => ws2_32.SD_RECEIVE, | 11227 | .recv => ws2_32.SD_RECEIVE, |
| ... | @@ -10880,27 +11229,27 @@ fn netShutdownWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net | ... | @@ -10880,27 +11229,27 @@ fn netShutdownWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net |
| 10880 | .both => ws2_32.SD_BOTH, | 11229 | .both => ws2_32.SD_BOTH, |
| 10881 | }; | 11230 | }; |
| 10882 | | 11231 | |
| 10883 | try current_thread.beginSyscall(); | 11232 | var syscall: Syscall = try .start(); |
| 10884 | while (true) { | 11233 | while (true) { |
| 10885 | const rc = ws2_32.shutdown(handle, wsa_how); | 11234 | const rc = ws2_32.shutdown(handle, wsa_how); |
| 10886 | if (rc != ws2_32.SOCKET_ERROR) { | 11235 | if (rc != ws2_32.SOCKET_ERROR) { |
| 10887 | current_thread.endSyscall(); | 11236 | syscall.finish(); |
| 10888 | return; | 11237 | return; |
| 10889 | } | 11238 | } |
| 10890 | switch (ws2_32.WSAGetLastError()) { | 11239 | switch (ws2_32.WSAGetLastError()) { |
| 10891 | .EINTR => { | 11240 | .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { |
| 10892 | try current_thread.checkCancel(); | 11241 | try syscall.checkCancel(); |
| 10893 | continue; | 11242 | continue; |
| 10894 | }, | 11243 | }, |
| 10895 | .NOTINITIALISED => { | 11244 | .NOTINITIALISED => { |
| | 11245 | syscall.finish(); |
| 10896 | try initializeWsa(t); | 11246 | try initializeWsa(t); |
| 10897 | try current_thread.checkCancel(); | 11247 | syscall = try .start(); |
| 10898 | continue; | 11248 | continue; |
| 10899 | }, | 11249 | }, |
| 10900 | else => |e| { | 11250 | else => |e| { |
| 10901 | current_thread.endSyscall(); | 11251 | syscall.finish(); |
| 10902 | switch (e) { | 11252 | switch (e) { |
| 10903 | .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, | | |
| 10904 | .ECONNABORTED => return error.ConnectionAborted, | 11253 | .ECONNABORTED => return error.ConnectionAborted, |
| 10905 | .ECONNRESET => return error.ConnectionResetByPeer, | 11254 | .ECONNRESET => return error.ConnectionResetByPeer, |
| 10906 | .ENETDOWN => return error.NetworkDown, | 11255 | .ENETDOWN => return error.NetworkDown, |
| ... | @@ -11093,18 +11442,17 @@ fn netLookupFallible( | ... | @@ -11093,18 +11442,17 @@ fn netLookupFallible( |
| 11093 | .provider = null, | 11442 | .provider = null, |
| 11094 | .next = null, | 11443 | .next = null, |
| 11095 | }; | 11444 | }; |
| 11096 | const cancel_handle: ?*windows.HANDLE = null; | | |
| 11097 | var res: *ws2_32.ADDRINFOEXW = undefined; | 11445 | var res: *ws2_32.ADDRINFOEXW = undefined; |
| 11098 | const timeout: ?*ws2_32.timeval = null; | 11446 | const timeout: ?*ws2_32.timeval = null; |
| 11099 | while (true) { | 11447 | while (true) { |
| | 11448 | // TODO: hook this up to cancelation with `Thread.Status.cancelation.blocked_windows_dns`. |
| | 11449 | // See matching TODO in `Thread.cancelAwaitable`. |
| 11100 | try Thread.checkCancel(); | 11450 | try Thread.checkCancel(); |
| 11101 | // TODO make this append to the queue eagerly rather than blocking until | 11451 | // TODO make this append to the queue eagerly rather than blocking until the whole thing finishes |
| 11102 | // 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)); |
| 11103 | const rc: ws2_32.WinsockError = @enumFromInt(ws2_32.GetAddrInfoExW(name_w, port_w, .DNS, null, &hints, &res, timeout, null, null, cancel_handle)); | | |
| 11104 | switch (rc) { | 11453 | switch (rc) { |
| 11105 | @as(ws2_32.WinsockError, @enumFromInt(0)) => break, | 11454 | @as(ws2_32.WinsockError, @enumFromInt(0)) => break, |
| 11106 | .EINTR => continue, | 11455 | .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => continue, |
| 11107 | .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, | | |
| 11108 | .NOTINITIALISED => { | 11456 | .NOTINITIALISED => { |
| 11109 | try initializeWsa(t); | 11457 | try initializeWsa(t); |
| 11110 | continue; | 11458 | continue; |
| ... | @@ -11352,29 +11700,33 @@ fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) std.process.SetCurrentD | ... | @@ -11352,29 +11700,33 @@ fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) std.process.SetCurrentD |
| 11352 | _ = t; | 11700 | _ = t; |
| 11353 | | 11701 | |
| 11354 | if (is_windows) { | 11702 | if (is_windows) { |
| 11355 | try Thread.checkCancel(); | | |
| 11356 | var dir_path_buffer: [windows.PATH_MAX_WIDE]u16 = undefined; | 11703 | var dir_path_buffer: [windows.PATH_MAX_WIDE]u16 = undefined; |
| 11357 | // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks | 11704 | // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks |
| | 11705 | try Thread.checkCancel(); |
| 11358 | const dir_path = try windows.GetFinalPathNameByHandle(dir.handle, .{}, &dir_path_buffer); | 11706 | const dir_path = try windows.GetFinalPathNameByHandle(dir.handle, .{}, &dir_path_buffer); |
| 11359 | const path_len_bytes = std.math.cast(u16, dir_path.len * 2) orelse return error.NameTooLong; | 11707 | const path_len_bytes = std.math.cast(u16, dir_path.len * 2) orelse return error.NameTooLong; |
| 11360 | try Thread.checkCancel(); | | |
| 11361 | var nt_name: windows.UNICODE_STRING = .{ | 11708 | var nt_name: windows.UNICODE_STRING = .{ |
| 11362 | .Length = path_len_bytes, | 11709 | .Length = path_len_bytes, |
| 11363 | .MaximumLength = path_len_bytes, | 11710 | .MaximumLength = path_len_bytes, |
| 11364 | .Buffer = @constCast(dir_path.ptr), | 11711 | .Buffer = @constCast(dir_path.ptr), |
| 11365 | }; | 11712 | }; |
| 11366 | switch (windows.ntdll.RtlSetCurrentDirectory_U(&nt_name)) { | 11713 | const syscall: Syscall = try .start(); |
| 11367 | .SUCCESS => return, | 11714 | while (true) switch (windows.ntdll.RtlSetCurrentDirectory_U(&nt_name)) { |
| 11368 | .OBJECT_NAME_INVALID => return error.BadPathName, | 11715 | .SUCCESS => return syscall.finish(), |
| 11369 | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, | 11716 | .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), |
| 11370 | .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, | 11717 | .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 11371 | .NO_MEDIA_IN_DEVICE => return error.NoDevice, | 11718 | .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 11372 | .INVALID_PARAMETER => |err| return windows.statusBug(err), | 11719 | .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice), |
| 11373 | .ACCESS_DENIED => return error.AccessDenied, | 11720 | .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), |
| 11374 | .OBJECT_PATH_SYNTAX_BAD => |err| return windows.statusBug(err), | 11721 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| 11375 | .NOT_A_DIRECTORY => return error.NotDir, | 11722 | .OBJECT_PATH_SYNTAX_BAD => |err| return syscall.ntstatusBug(err), |
| 11376 | else => |status| return windows.unexpectedStatus(status), | 11723 | .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), |
| 11377 | } | 11724 | .CANCELLED => { |
| | 11725 | try syscall.checkCancel(); |
| | 11726 | continue; |
| | 11727 | }, |
| | 11728 | else => |status| return syscall.unexpectedNtstatus(status), |
| | 11729 | }; |
| 11378 | } | 11730 | } |
| 11379 | | 11731 | |
| 11380 | if (dir.handle == posix.AT.FDCWD) return; | 11732 | if (dir.handle == posix.AT.FDCWD) return; |
| ... | @@ -12185,391 +12537,6 @@ fn initializeWsa(t: *Threaded) error{ NetworkDown, Canceled }!void { | ... | @@ -12185,391 +12537,6 @@ fn initializeWsa(t: *Threaded) error{ NetworkDown, Canceled }!void { |
| 12185 | | 12537 | |
| 12186 | fn doNothingSignalHandler(_: posix.SIG) callconv(.c) void {} | 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 | fn scanEnviron(t: *Threaded) void { | 12540 | fn scanEnviron(t: *Threaded) void { |
| 12574 | t.mutex.lock(); | 12541 | t.mutex.lock(); |
| 12575 | defer t.mutex.unlock(); | 12542 | defer t.mutex.unlock(); |
| ... | @@ -12688,3 +12655,459 @@ fn scanEnviron(t: *Threaded) void { | ... | @@ -12688,3 +12655,459 @@ fn scanEnviron(t: *Threaded) void { |
| 12688 | test { | 12655 | test { |
| 12689 | _ = @import("Threaded/test.zig"); | 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 | } |