authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-26 16:12:09-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-26 19:58:56-08:00
logf160d5f979c9f2bcc98ef0169a879a76d0234ed2
tree0d0ffdb2d1399ed0001c792d81d11491743bab58
parentfeeed922e1a7d89b99572d5577c814f9e6293b9b

std.Io.Threaded: implement file writing for Windows


3 files changed, 92 insertions(+), 167 deletions(-)

lib/std/Io/Threaded.zig+88-2
...@@ -7420,7 +7420,18 @@ fn fileWritePositional(...@@ -7420,7 +7420,18 @@ fn fileWritePositional(
7420 const t: *Threaded = @ptrCast(@alignCast(userdata));7420 const t: *Threaded = @ptrCast(@alignCast(userdata));
7421 const current_thread = Thread.getCurrent(t);7421 const current_thread = Thread.getCurrent(t);
74227422
7423 if (is_windows) @panic("TODO implement fileWritePositional windows");7423 if (is_windows) {
7424 if (header.len != 0) {
7425 return writeFilePositionalWindows(current_thread, file.handle, header, offset);
7426 }
7427 for (data[0 .. data.len - 1]) |buf| {
7428 if (buf.len == 0) continue;
7429 return writeFilePositionalWindows(current_thread, file.handle, buf, offset);
7430 }
7431 const pattern = data[data.len - 1];
7432 if (pattern.len == 0 or splat == 0) return 0;
7433 return writeFilePositionalWindows(current_thread, file.handle, pattern, offset);
7434 }
74247435
7425 var iovecs: [max_iovecs_len]posix.iovec_const = undefined;7436 var iovecs: [max_iovecs_len]posix.iovec_const = undefined;
7426 var iovlen: iovlen_t = 0;7437 var iovlen: iovlen_t = 0;
...@@ -7532,6 +7543,44 @@ fn fileWritePositional(...@@ -7532,6 +7543,44 @@ fn fileWritePositional(
7532 }7543 }
7533}7544}
75347545
7546fn writeFilePositionalWindows(
7547 current_thread: *Thread,
7548 handle: windows.HANDLE,
7549 bytes: []const u8,
7550 offset: u64,
7551) File.WritePositionalError!usize {
7552 try current_thread.checkCancel();
7553
7554 var bytes_written: windows.DWORD = undefined;
7555 var overlapped: windows.OVERLAPPED = .{
7556 .Internal = 0,
7557 .InternalHigh = 0,
7558 .DUMMYUNIONNAME = .{
7559 .DUMMYSTRUCTNAME = .{
7560 .Offset = @truncate(offset),
7561 .OffsetHigh = @truncate(offset >> 32),
7562 },
7563 },
7564 .hEvent = null,
7565 };
7566 const adjusted_len = std.math.lossyCast(u32, bytes.len);
7567 if (windows.kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, &overlapped) == 0) {
7568 switch (windows.GetLastError()) {
7569 .INVALID_USER_BUFFER => return error.SystemResources,
7570 .NOT_ENOUGH_MEMORY => return error.SystemResources,
7571 .OPERATION_ABORTED => return error.Canceled,
7572 .NOT_ENOUGH_QUOTA => return error.SystemResources,
7573 .NO_DATA => return error.BrokenPipe,
7574 .INVALID_HANDLE => return error.NotOpenForWriting,
7575 .LOCK_VIOLATION => return error.LockViolation,
7576 .ACCESS_DENIED => return error.AccessDenied,
7577 .WORKING_SET_QUOTA => return error.SystemResources,
7578 else => |err| return windows.unexpectedError(err),
7579 }
7580 }
7581 return bytes_written;
7582}
7583
7535fn fileWriteStreaming(7584fn fileWriteStreaming(
7536 userdata: ?*anyopaque,7585 userdata: ?*anyopaque,
7537 file: File,7586 file: File,
...@@ -7542,7 +7591,18 @@ fn fileWriteStreaming(...@@ -7542,7 +7591,18 @@ fn fileWriteStreaming(
7542 const t: *Threaded = @ptrCast(@alignCast(userdata));7591 const t: *Threaded = @ptrCast(@alignCast(userdata));
7543 const current_thread = Thread.getCurrent(t);7592 const current_thread = Thread.getCurrent(t);
75447593
7545 if (is_windows) @panic("TODO implement fileWriteStreaming windows");7594 if (is_windows) {
7595 if (header.len != 0) {
7596 return writeFileStreamingWindows(current_thread, file.handle, header);
7597 }
7598 for (data[0 .. data.len - 1]) |buf| {
7599 if (buf.len == 0) continue;
7600 return writeFileStreamingWindows(current_thread, file.handle, buf);
7601 }
7602 const pattern = data[data.len - 1];
7603 if (pattern.len == 0 or splat == 0) return 0;
7604 return writeFileStreamingWindows(current_thread, file.handle, pattern);
7605 }
75467606
7547 var iovecs: [max_iovecs_len]posix.iovec_const = undefined;7607 var iovecs: [max_iovecs_len]posix.iovec_const = undefined;
7548 var iovlen: iovlen_t = 0;7608 var iovlen: iovlen_t = 0;
...@@ -7647,6 +7707,32 @@ fn fileWriteStreaming(...@@ -7647,6 +7707,32 @@ fn fileWriteStreaming(
7647 }7707 }
7648}7708}
76497709
7710fn writeFileStreamingWindows(
7711 current_thread: *Thread,
7712 handle: windows.HANDLE,
7713 bytes: []const u8,
7714) File.Writer.Error!usize {
7715 try current_thread.checkCancel();
7716
7717 var bytes_written: windows.DWORD = undefined;
7718 const adjusted_len = std.math.lossyCast(u32, bytes.len);
7719 if (windows.kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, null) == 0) {
7720 switch (windows.GetLastError()) {
7721 .INVALID_USER_BUFFER => return error.SystemResources,
7722 .NOT_ENOUGH_MEMORY => return error.SystemResources,
7723 .OPERATION_ABORTED => return error.Canceled,
7724 .NOT_ENOUGH_QUOTA => return error.SystemResources,
7725 .NO_DATA => return error.BrokenPipe,
7726 .INVALID_HANDLE => return error.NotOpenForWriting,
7727 .LOCK_VIOLATION => return error.LockViolation,
7728 .ACCESS_DENIED => return error.AccessDenied,
7729 .WORKING_SET_QUOTA => return error.SystemResources,
7730 else => |err| return windows.unexpectedError(err),
7731 }
7732 }
7733 return bytes_written;
7734}
7735
7650fn fileWriteFileStreaming(7736fn fileWriteFileStreaming(
7651 userdata: ?*anyopaque,7737 userdata: ?*anyopaque,
7652 file: File,7738 file: File,
lib/std/os/windows.zig-109
...@@ -2830,115 +2830,6 @@ pub fn CloseHandle(hObject: HANDLE) void {...@@ -2830,115 +2830,6 @@ pub fn CloseHandle(hObject: HANDLE) void {
2830 assert(ntdll.NtClose(hObject) == .SUCCESS);2830 assert(ntdll.NtClose(hObject) == .SUCCESS);
2831}2831}
28322832
2833pub const ReadFileError = error{
2834 BrokenPipe,
2835 /// The specified network name is no longer available.
2836 ConnectionResetByPeer,
2837 Canceled,
2838 /// Unable to read file due to lock.
2839 LockViolation,
2840 /// Known to be possible when:
2841 /// - Unable to read from disconnected virtual com port (Windows)
2842 AccessDenied,
2843 NotOpenForReading,
2844 Unexpected,
2845};
2846
2847/// If buffer's length exceeds what a Windows DWORD integer can hold, it will be broken into
2848/// multiple non-atomic reads.
2849pub fn ReadFile(in_hFile: HANDLE, buffer: []u8, offset: ?u64) ReadFileError!usize {
2850 while (true) {
2851 const want_read_count: DWORD = @min(@as(DWORD, maxInt(DWORD)), buffer.len);
2852 var amt_read: DWORD = undefined;
2853 var overlapped_data: OVERLAPPED = undefined;
2854 const overlapped: ?*OVERLAPPED = if (offset) |off| blk: {
2855 overlapped_data = .{
2856 .Internal = 0,
2857 .InternalHigh = 0,
2858 .DUMMYUNIONNAME = .{
2859 .DUMMYSTRUCTNAME = .{
2860 .Offset = @as(u32, @truncate(off)),
2861 .OffsetHigh = @as(u32, @truncate(off >> 32)),
2862 },
2863 },
2864 .hEvent = null,
2865 };
2866 break :blk &overlapped_data;
2867 } else null;
2868 if (kernel32.ReadFile(in_hFile, buffer.ptr, want_read_count, &amt_read, overlapped) == 0) {
2869 switch (GetLastError()) {
2870 .IO_PENDING => unreachable,
2871 .OPERATION_ABORTED => continue,
2872 .BROKEN_PIPE => return 0,
2873 .HANDLE_EOF => return 0,
2874 .NETNAME_DELETED => return error.ConnectionResetByPeer,
2875 .LOCK_VIOLATION => return error.LockViolation,
2876 .ACCESS_DENIED => return error.AccessDenied,
2877 .INVALID_HANDLE => return error.NotOpenForReading,
2878 else => |err| return unexpectedError(err),
2879 }
2880 }
2881 return amt_read;
2882 }
2883}
2884
2885pub const WriteFileError = error{
2886 SystemResources,
2887 Canceled,
2888 BrokenPipe,
2889 NotOpenForWriting,
2890 /// The process cannot access the file because another process has locked
2891 /// a portion of the file.
2892 LockViolation,
2893 /// The specified network name is no longer available.
2894 ConnectionResetByPeer,
2895 /// Known to be possible when:
2896 /// - Unable to write to disconnected virtual com port (Windows)
2897 AccessDenied,
2898 Unexpected,
2899};
2900
2901pub fn WriteFile(
2902 handle: HANDLE,
2903 bytes: []const u8,
2904 offset: ?u64,
2905) WriteFileError!usize {
2906 var bytes_written: DWORD = undefined;
2907 var overlapped_data: OVERLAPPED = undefined;
2908 const overlapped: ?*OVERLAPPED = if (offset) |off| blk: {
2909 overlapped_data = .{
2910 .Internal = 0,
2911 .InternalHigh = 0,
2912 .DUMMYUNIONNAME = .{
2913 .DUMMYSTRUCTNAME = .{
2914 .Offset = @truncate(off),
2915 .OffsetHigh = @truncate(off >> 32),
2916 },
2917 },
2918 .hEvent = null,
2919 };
2920 break :blk &overlapped_data;
2921 } else null;
2922 const adjusted_len = math.cast(u32, bytes.len) orelse maxInt(u32);
2923 if (kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, overlapped) == 0) {
2924 switch (GetLastError()) {
2925 .INVALID_USER_BUFFER => return error.SystemResources,
2926 .NOT_ENOUGH_MEMORY => return error.SystemResources,
2927 .OPERATION_ABORTED => return error.Canceled,
2928 .NOT_ENOUGH_QUOTA => return error.SystemResources,
2929 .IO_PENDING => unreachable,
2930 .NO_DATA => return error.BrokenPipe,
2931 .INVALID_HANDLE => return error.NotOpenForWriting,
2932 .LOCK_VIOLATION => return error.LockViolation,
2933 .NETNAME_DELETED => return error.ConnectionResetByPeer,
2934 .ACCESS_DENIED => return error.AccessDenied,
2935 .WORKING_SET_QUOTA => return error.SystemResources,
2936 else => |err| return unexpectedError(err),
2937 }
2938 }
2939 return bytes_written;
2940}
2941
2942pub const GetCurrentDirectoryError = error{2833pub const GetCurrentDirectoryError = error{
2943 NameTooLong,2834 NameTooLong,
2944 Unexpected,2835 Unexpected,
lib/std/posix.zig+4-56
...@@ -486,34 +486,8 @@ pub const ReadError = std.Io.File.Reader.Error;...@@ -486,34 +486,8 @@ pub const ReadError = std.Io.File.Reader.Error;
486/// The corresponding POSIX limit is `maxInt(isize)`.486/// The corresponding POSIX limit is `maxInt(isize)`.
487pub fn read(fd: fd_t, buf: []u8) ReadError!usize {487pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
488 if (buf.len == 0) return 0;488 if (buf.len == 0) return 0;
489 if (native_os == .windows) {489 if (native_os == .windows) @compileError("unsupported OS");
490 return windows.ReadFile(fd, buf, null);490 if (native_os == .wasi) @compileError("unsupported OS");
491 }
492 if (native_os == .wasi and !builtin.link_libc) {
493 const iovs = [1]iovec{iovec{
494 .base = buf.ptr,
495 .len = buf.len,
496 }};
497
498 var nread: usize = undefined;
499 switch (wasi.fd_read(fd, &iovs, iovs.len, &nread)) {
500 .SUCCESS => return nread,
501 .INTR => unreachable,
502 .INVAL => unreachable,
503 .FAULT => unreachable,
504 .AGAIN => unreachable,
505 .BADF => return error.NotOpenForReading, // Can be a race condition.
506 .IO => return error.InputOutput,
507 .ISDIR => return error.IsDir,
508 .NOBUFS => return error.SystemResources,
509 .NOMEM => return error.SystemResources,
510 .NOTCONN => return error.SocketUnconnected,
511 .CONNRESET => return error.ConnectionResetByPeer,
512 .TIMEDOUT => return error.Timeout,
513 .NOTCAPABLE => return error.AccessDenied,
514 else => |err| return unexpectedErrno(err),
515 }
516 }
517491
518 // Prevents EINVAL.492 // Prevents EINVAL.
519 const max_count = switch (native_os) {493 const max_count = switch (native_os) {
...@@ -606,34 +580,8 @@ pub const WriteError = error{...@@ -606,34 +580,8 @@ pub const WriteError = error{
606/// The corresponding POSIX limit is `maxInt(isize)`.580/// The corresponding POSIX limit is `maxInt(isize)`.
607pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {581pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {
608 if (bytes.len == 0) return 0;582 if (bytes.len == 0) return 0;
609 if (native_os == .windows) {583 if (native_os == .windows) @compileError("unsupported OS");
610 return windows.WriteFile(fd, bytes, null);584 if (native_os == .wasi) @compileError("unsupported OS");
611 }
612
613 if (native_os == .wasi and !builtin.link_libc) {
614 const ciovs = [_]iovec_const{iovec_const{
615 .base = bytes.ptr,
616 .len = bytes.len,
617 }};
618 var nwritten: usize = undefined;
619 switch (wasi.fd_write(fd, &ciovs, ciovs.len, &nwritten)) {
620 .SUCCESS => return nwritten,
621 .INTR => unreachable,
622 .INVAL => unreachable,
623 .FAULT => unreachable,
624 .AGAIN => unreachable,
625 .BADF => return error.NotOpenForWriting, // can be a race condition.
626 .DESTADDRREQ => unreachable, // `connect` was never called.
627 .DQUOT => return error.DiskQuota,
628 .FBIG => return error.FileTooBig,
629 .IO => return error.InputOutput,
630 .NOSPC => return error.NoSpaceLeft,
631 .PERM => return error.PermissionDenied,
632 .PIPE => return error.BrokenPipe,
633 .NOTCAPABLE => return error.AccessDenied,
634 else => |err| return unexpectedErrno(err),
635 }
636 }
637585
638 const max_count = switch (native_os) {586 const max_count = switch (native_os) {
639 .linux => 0x7ffff000,587 .linux => 0x7ffff000,