| ... | @@ -1649,7 +1649,6 @@ pub fn io(t: *Threaded) Io { | ... | @@ -1649,7 +1649,6 @@ pub fn io(t: *Threaded) Io { |
| 1649 | .fileStat = fileStat, | 1649 | .fileStat = fileStat, |
| 1650 | .fileLength = fileLength, | 1650 | .fileLength = fileLength, |
| 1651 | .fileClose = fileClose, | 1651 | .fileClose = fileClose, |
| 1652 | .fileWriteStreaming = fileWriteStreaming, | | |
| 1653 | .fileWritePositional = fileWritePositional, | 1652 | .fileWritePositional = fileWritePositional, |
| 1654 | .fileWriteFileStreaming = fileWriteFileStreaming, | 1653 | .fileWriteFileStreaming = fileWriteFileStreaming, |
| 1655 | .fileWriteFilePositional = fileWriteFilePositional, | 1654 | .fileWriteFilePositional = fileWriteFilePositional, |
| ... | @@ -1813,7 +1812,6 @@ pub fn ioBasic(t: *Threaded) Io { | ... | @@ -1813,7 +1812,6 @@ pub fn ioBasic(t: *Threaded) Io { |
| 1813 | .fileStat = fileStat, | 1812 | .fileStat = fileStat, |
| 1814 | .fileLength = fileLength, | 1813 | .fileLength = fileLength, |
| 1815 | .fileClose = fileClose, | 1814 | .fileClose = fileClose, |
| 1816 | .fileWriteStreaming = fileWriteStreaming, | | |
| 1817 | .fileWritePositional = fileWritePositional, | 1815 | .fileWritePositional = fileWritePositional, |
| 1818 | .fileWriteFileStreaming = fileWriteFileStreaming, | 1816 | .fileWriteFileStreaming = fileWriteFileStreaming, |
| 1819 | .fileWriteFilePositional = fileWriteFilePositional, | 1817 | .fileWriteFilePositional = fileWriteFilePositional, |
| ... | @@ -2496,6 +2494,12 @@ fn operate(userdata: ?*anyopaque, operation: Io.Operation) Io.Cancelable!Io.Oper | ... | @@ -2496,6 +2494,12 @@ fn operate(userdata: ?*anyopaque, operation: Io.Operation) Io.Cancelable!Io.Oper |
| 2496 | else => |e| e, | 2494 | else => |e| e, |
| 2497 | }, | 2495 | }, |
| 2498 | }, | 2496 | }, |
| | 2497 | .file_write_streaming => |o| return .{ |
| | 2498 | .file_write_streaming = fileWriteStreaming(t, o.file, o.header, o.data, o.splat) catch |err| switch (err) { |
| | 2499 | error.Canceled => |e| return e, |
| | 2500 | else => |e| e, |
| | 2501 | }, |
| | 2502 | }, |
| 2499 | } | 2503 | } |
| 2500 | } | 2504 | } |
| 2501 | | 2505 | |
| ... | @@ -2523,6 +2527,10 @@ fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Cancelable!void { | ... | @@ -2523,6 +2527,10 @@ fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Cancelable!void { |
| 2523 | poll_buffer[poll_len] = .{ .fd = o.file.handle, .events = posix.POLL.IN, .revents = 0 }; | 2527 | poll_buffer[poll_len] = .{ .fd = o.file.handle, .events = posix.POLL.IN, .revents = 0 }; |
| 2524 | poll_len += 1; | 2528 | poll_len += 1; |
| 2525 | }, | 2529 | }, |
| | 2530 | .file_write_streaming => |o| { |
| | 2531 | poll_buffer[poll_len] = .{ .fd = o.file.handle, .events = posix.POLL.OUT, .revents = 0 }; |
| | 2532 | poll_len += 1; |
| | 2533 | }, |
| 2526 | } | 2534 | } |
| 2527 | index = submission.node.next; | 2535 | index = submission.node.next; |
| 2528 | } | 2536 | } |
| ... | @@ -2687,6 +2695,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout | ... | @@ -2687,6 +2695,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout |
| 2687 | const submission = &b.storage[index.toIndex()].submission; | 2695 | const submission = &b.storage[index.toIndex()].submission; |
| 2688 | switch (submission.operation) { | 2696 | switch (submission.operation) { |
| 2689 | .file_read_streaming => |o| try poll_storage.add(o.file, posix.POLL.IN), | 2697 | .file_read_streaming => |o| try poll_storage.add(o.file, posix.POLL.IN), |
| | 2698 | .file_write_streaming => |o| try poll_storage.add(o.file, posix.POLL.OUT), |
| 2690 | } | 2699 | } |
| 2691 | index = submission.node.next; | 2700 | index = submission.node.next; |
| 2692 | } | 2701 | } |
| ... | @@ -2864,6 +2873,7 @@ fn batchApc(apc_context: ?*anyopaque, iosb: *windows.IO_STATUS_BLOCK, _: windows | ... | @@ -2864,6 +2873,7 @@ fn batchApc(apc_context: ?*anyopaque, iosb: *windows.IO_STATUS_BLOCK, _: windows |
| 2864 | b.completions.tail = .fromIndex(index); | 2873 | b.completions.tail = .fromIndex(index); |
| 2865 | const result: Io.Operation.Result = switch (pending.tag) { | 2874 | const result: Io.Operation.Result = switch (pending.tag) { |
| 2866 | .file_read_streaming => .{ .file_read_streaming = ntReadFileResult(iosb) }, | 2875 | .file_read_streaming => .{ .file_read_streaming = ntReadFileResult(iosb) }, |
| | 2876 | .file_write_streaming => .{ .file_write_streaming = ntWriteFileResult(iosb) }, |
| 2867 | }; | 2877 | }; |
| 2868 | storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } }; | 2878 | storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } }; |
| 2869 | }, | 2879 | }, |
| ... | @@ -2950,6 +2960,66 @@ fn batchAwaitWindows(b: *Io.Batch, concurrency: bool) error{ Canceled, Concurren | ... | @@ -2950,6 +2960,66 @@ fn batchAwaitWindows(b: *Io.Batch, concurrency: bool) error{ Canceled, Concurren |
| 2950 | else => |status| { | 2960 | else => |status| { |
| 2951 | syscall.finish(); | 2961 | syscall.finish(); |
| 2952 | | 2962 | |
| | 2963 | context.iosb.u.Status = status; |
| | 2964 | batchApc(b, &context.iosb, 0); |
| | 2965 | break; |
| | 2966 | }, |
| | 2967 | }; |
| | 2968 | } |
| | 2969 | }, |
| | 2970 | .file_write_streaming => |o| o: { |
| | 2971 | const buffer = windowsWriteBuffer(o.header, o.data, o.splat); |
| | 2972 | if (buffer.len == 0) { |
| | 2973 | context.iosb = .{ |
| | 2974 | .u = .{ .Status = .SUCCESS }, |
| | 2975 | .Information = 0, |
| | 2976 | }; |
| | 2977 | batchApc(b, &context.iosb, 0); |
| | 2978 | break :o; |
| | 2979 | } |
| | 2980 | if (o.file.flags.nonblocking) { |
| | 2981 | context.file = o.file.handle; |
| | 2982 | switch (windows.ntdll.NtWriteFile( |
| | 2983 | o.file.handle, |
| | 2984 | null, // event |
| | 2985 | &batchApc, |
| | 2986 | b, |
| | 2987 | &context.iosb, |
| | 2988 | buffer.ptr, |
| | 2989 | @intCast(buffer.len), |
| | 2990 | null, // byte offset |
| | 2991 | null, // key |
| | 2992 | )) { |
| | 2993 | .PENDING, .SUCCESS => {}, |
| | 2994 | .CANCELLED => unreachable, |
| | 2995 | else => |status| { |
| | 2996 | context.iosb.u.Status = status; |
| | 2997 | batchApc(b, &context.iosb, 0); |
| | 2998 | }, |
| | 2999 | } |
| | 3000 | } else { |
| | 3001 | if (concurrency) return error.ConcurrencyUnavailable; |
| | 3002 | |
| | 3003 | const syscall: Syscall = try .start(); |
| | 3004 | while (true) switch (windows.ntdll.NtWriteFile( |
| | 3005 | o.file.handle, |
| | 3006 | null, // event |
| | 3007 | null, // APC routine |
| | 3008 | null, // APC context |
| | 3009 | &context.iosb, |
| | 3010 | buffer.ptr, |
| | 3011 | @intCast(buffer.len), |
| | 3012 | null, // byte offset |
| | 3013 | null, // key |
| | 3014 | )) { |
| | 3015 | .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag |
| | 3016 | .CANCELLED => { |
| | 3017 | try syscall.checkCancel(); |
| | 3018 | continue; |
| | 3019 | }, |
| | 3020 | else => |status| { |
| | 3021 | syscall.finish(); |
| | 3022 | |
| 2953 | context.iosb.u.Status = status; | 3023 | context.iosb.u.Status = status; |
| 2954 | batchApc(b, &context.iosb, 0); | 3024 | batchApc(b, &context.iosb, 0); |
| 2955 | break; | 3025 | break; |
| ... | @@ -2963,6 +3033,21 @@ fn batchAwaitWindows(b: *Io.Batch, concurrency: bool) error{ Canceled, Concurren | ... | @@ -2963,6 +3033,21 @@ fn batchAwaitWindows(b: *Io.Batch, concurrency: bool) error{ Canceled, Concurren |
| 2963 | b.submissions = .{ .head = .none, .tail = .none }; | 3033 | b.submissions = .{ .head = .none, .tail = .none }; |
| 2964 | } | 3034 | } |
| 2965 | | 3035 | |
| | 3036 | /// Since Windows only supports writing one contiguous buffer, returns the |
| | 3037 | /// first one, while also limiting it to a length representable by 32-bit |
| | 3038 | /// unsigned integer. |
| | 3039 | fn windowsWriteBuffer(header: []const u8, data: []const []const u8, splat: usize) []const u8 { |
| | 3040 | const buffer = b: { |
| | 3041 | if (header.len != 0) break :b header; |
| | 3042 | for (data[0 .. data.len - 1]) |buffer| { |
| | 3043 | if (buffer.len != 0) break :b buffer; |
| | 3044 | } |
| | 3045 | if (splat == 0) return &.{}; |
| | 3046 | break :b data[data.len - 1]; |
| | 3047 | }; |
| | 3048 | return buffer[0..@min(buffer.len, std.math.maxInt(u32))]; |
| | 3049 | } |
| | 3050 | |
| 2966 | fn submitComplete(ring: []u32, complete_tail: *Io.Batch.RingIndex, op: u32) void { | 3051 | fn submitComplete(ring: []u32, complete_tail: *Io.Batch.RingIndex, op: u32) void { |
| 2967 | const ct = complete_tail.*; | 3052 | const ct = complete_tail.*; |
| 2968 | const len: u31 = @intCast(ring.len); | 3053 | const len: u31 = @intCast(ring.len); |
| ... | @@ -9005,6 +9090,24 @@ fn ntReadFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize { | ... | @@ -9005,6 +9090,24 @@ fn ntReadFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize { |
| 9005 | } | 9090 | } |
| 9006 | } | 9091 | } |
| 9007 | | 9092 | |
| | 9093 | fn ntWriteFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize { |
| | 9094 | switch (io_status_block.u.Status) { |
| | 9095 | .PENDING => unreachable, |
| | 9096 | .CANCELLED => unreachable, |
| | 9097 | .SUCCESS => return io_status_block.Information, |
| | 9098 | .INVALID_USER_BUFFER => return error.SystemResources, |
| | 9099 | .NO_MEMORY => return error.SystemResources, |
| | 9100 | .QUOTA_EXCEEDED => return error.SystemResources, |
| | 9101 | .PIPE_BROKEN => return error.BrokenPipe, |
| | 9102 | .INVALID_HANDLE => return error.NotOpenForWriting, |
| | 9103 | .LOCK_NOT_GRANTED => return error.LockViolation, |
| | 9104 | .ACCESS_DENIED => return error.AccessDenied, |
| | 9105 | .WORKING_SET_QUOTA => return error.SystemResources, |
| | 9106 | .DISK_FULL => return error.NoSpaceLeft, |
| | 9107 | else => |status| return windows.unexpectedStatus(status), |
| | 9108 | } |
| | 9109 | } |
| | 9110 | |
| 9008 | fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize { | 9111 | fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize { |
| 9009 | if (!have_preadv) @compileError("TODO implement fileReadPositionalPosix for cursed operating systems that don't support preadv (it's only Haiku)"); | 9112 | if (!have_preadv) @compileError("TODO implement fileReadPositionalPosix for cursed operating systems that don't support preadv (it's only Haiku)"); |
| 9010 | | 9113 | |
| ... | @@ -9837,16 +9940,9 @@ fn fileWriteStreaming( | ... | @@ -9837,16 +9940,9 @@ fn fileWriteStreaming( |
| 9837 | _ = t; | 9940 | _ = t; |
| 9838 | | 9941 | |
| 9839 | if (is_windows) { | 9942 | if (is_windows) { |
| 9840 | if (header.len != 0) { | 9943 | const buffer = windowsWriteBuffer(header, data, splat); |
| 9841 | return writeFileStreamingWindows(file.handle, header); | 9944 | if (buffer.len == 0) return 0; |
| 9842 | } | 9945 | return fileWriteStreamingWindows(file, buffer); |
| 9843 | for (data[0 .. data.len - 1]) |buf| { | | |
| 9844 | if (buf.len == 0) continue; | | |
| 9845 | return writeFileStreamingWindows(file.handle, buf); | | |
| 9846 | } | | |
| 9847 | const pattern = data[data.len - 1]; | | |
| 9848 | if (pattern.len == 0 or splat == 0) return 0; | | |
| 9849 | return writeFileStreamingWindows(file.handle, pattern); | | |
| 9850 | } | 9946 | } |
| 9851 | | 9947 | |
| 9852 | var iovecs: [max_iovecs_len]posix.iovec_const = undefined; | 9948 | var iovecs: [max_iovecs_len]posix.iovec_const = undefined; |
| ... | @@ -9953,38 +10049,66 @@ fn fileWriteStreaming( | ... | @@ -9953,38 +10049,66 @@ fn fileWriteStreaming( |
| 9953 | } | 10049 | } |
| 9954 | } | 10050 | } |
| 9955 | | 10051 | |
| 9956 | fn writeFileStreamingWindows( | 10052 | fn fileWriteStreamingWindows(file: File, buffer: []const u8) File.Writer.Error!usize { |
| 9957 | handle: windows.HANDLE, | 10053 | assert(buffer.len != 0); |
| 9958 | bytes: []const u8, | 10054 | |
| 9959 | ) File.Writer.Error!usize { | 10055 | var iosb: windows.IO_STATUS_BLOCK = undefined; |
| 9960 | assert(bytes.len != 0); | 10056 | |
| 9961 | var bytes_written: windows.DWORD = undefined; | 10057 | if (file.flags.nonblocking) { |
| 9962 | const adjusted_len = std.math.lossyCast(u32, bytes.len); | 10058 | var done: bool = false; |
| 9963 | const syscall: Syscall = try .start(); | 10059 | switch (windows.ntdll.NtWriteFile( |
| 9964 | while (true) { | 10060 | file.handle, |
| 9965 | if (windows.kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, null) != 0) { | 10061 | null, // event |
| 9966 | syscall.finish(); | 10062 | flagApc, |
| 9967 | return bytes_written; | 10063 | &done, // APC context |
| | 10064 | &iosb, |
| | 10065 | buffer.ptr, |
| | 10066 | @intCast(buffer.len), |
| | 10067 | null, // byte offset |
| | 10068 | null, // key |
| | 10069 | )) { |
| | 10070 | // We must wait for the APC routine. |
| | 10071 | .PENDING, .SUCCESS => while (!done) { |
| | 10072 | // Once we get here we must not return from the function until the |
| | 10073 | // operation completes, thereby releasing reference to io_status_block. |
| | 10074 | const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) { |
| | 10075 | error.Canceled => |e| { |
| | 10076 | var cancel_iosb: windows.IO_STATUS_BLOCK = undefined; |
| | 10077 | _ = windows.ntdll.NtCancelIoFileEx(file.handle, &iosb, &cancel_iosb); |
| | 10078 | while (!done) waitForApcOrAlert(); |
| | 10079 | return e; |
| | 10080 | }, |
| | 10081 | }; |
| | 10082 | waitForApcOrAlert(); |
| | 10083 | alertable_syscall.finish(); |
| | 10084 | }, |
| | 10085 | else => |status| iosb.u.Status = status, |
| 9968 | } | 10086 | } |
| 9969 | switch (windows.GetLastError()) { | 10087 | return ntWriteFileResult(&iosb); |
| 9970 | .OPERATION_ABORTED => { | 10088 | } else { |
| | 10089 | const syscall: Syscall = try .start(); |
| | 10090 | while (true) switch (windows.ntdll.NtWriteFile( |
| | 10091 | file.handle, |
| | 10092 | null, // event |
| | 10093 | null, // APC routine |
| | 10094 | null, // APC context |
| | 10095 | &iosb, |
| | 10096 | buffer.ptr, |
| | 10097 | @intCast(buffer.len), |
| | 10098 | null, // byte offset |
| | 10099 | null, // key |
| | 10100 | )) { |
| | 10101 | .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag |
| | 10102 | .CANCELLED => { |
| 9971 | try syscall.checkCancel(); | 10103 | try syscall.checkCancel(); |
| 9972 | continue; | 10104 | continue; |
| 9973 | }, | 10105 | }, |
| 9974 | .INVALID_USER_BUFFER => return syscall.fail(error.SystemResources), | 10106 | else => |status| { |
| 9975 | .NOT_ENOUGH_MEMORY => return syscall.fail(error.SystemResources), | | |
| 9976 | .NOT_ENOUGH_QUOTA => return syscall.fail(error.SystemResources), | | |
| 9977 | .NO_DATA => return syscall.fail(error.BrokenPipe), | | |
| 9978 | .INVALID_HANDLE => return syscall.fail(error.NotOpenForWriting), | | |
| 9979 | .LOCK_VIOLATION => return syscall.fail(error.LockViolation), | | |
| 9980 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), | | |
| 9981 | .WORKING_SET_QUOTA => return syscall.fail(error.SystemResources), | | |
| 9982 | .DISK_FULL => return syscall.fail(error.NoSpaceLeft), | | |
| 9983 | else => |err| { | | |
| 9984 | syscall.finish(); | 10107 | syscall.finish(); |
| 9985 | return windows.unexpectedError(err); | 10108 | iosb.u.Status = status; |
| | 10109 | return ntWriteFileResult(&iosb); |
| 9986 | }, | 10110 | }, |
| 9987 | } | 10111 | }; |
| 9988 | } | 10112 | } |
| 9989 | } | 10113 | } |
| 9990 | | 10114 | |