| author | |
| committer | |
| log | 8a80b5464022a9fb4320e37f9dfc2aa83539ff07 |
| tree | 0fd5f5d40f3e9d1f600622f1c3554e95c7129aed |
| parent | fdf1ee973e9f3cb01f6fec9e460950622cdf92e4 |
and make reading file streaming allowed to return 0 byte reads.
According to Microsoft documentation, on Windows it is possible to get
0-byte reads from pipes when 0-byte writes are made.8 files changed, 82 insertions(+), 96 deletions(-)
lib/std/Io.zig+4-4| ... | ... | @@ -187,7 +187,7 @@ pub const VTable = struct { |
| 187 | 187 | fileWritePositional: *const fn (?*anyopaque, File, header: []const u8, data: []const []const u8, splat: usize, offset: u64) File.WritePositionalError!usize, |
| 188 | 188 | fileWriteFileStreaming: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit) File.Writer.WriteFileError!usize, |
| 189 | 189 | fileWriteFilePositional: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit, offset: u64) File.WriteFilePositionalError!usize, |
| 190 | /// Returns 0 on end of stream. | |
| 190 | /// Returns 0 if reading at or past the end. | |
| 191 | 191 | fileReadPositional: *const fn (?*anyopaque, File, data: []const []u8, offset: u64) File.ReadPositionalError!usize, |
| 192 | 192 | fileSeekBy: *const fn (?*anyopaque, File, relative_offset: i64) File.SeekError!void, |
| 193 | 193 | fileSeekTo: *const fn (?*anyopaque, File, absolute_offset: u64) File.SeekError!void, |
| ... | ... | @@ -263,18 +263,18 @@ pub const Operation = union(enum) { |
| 263 | 263 | status: Status(void) = .{ .unstarted = {} }, |
| 264 | 264 | }; |
| 265 | 265 | |
| 266 | /// Returns 0 on end of stream. | |
| 266 | /// May return 0 reads which is different than `error.EndOfStream`. | |
| 267 | 267 | pub const FileReadStreaming = struct { |
| 268 | 268 | file: File, |
| 269 | 269 | data: []const []u8, |
| 270 | 270 | status: Status(Error!usize) = .{ .unstarted = {} }, |
| 271 | 271 | |
| 272 | pub const Error = error{ | |
| 272 | pub const Error = UnendingError || error{EndOfStream}; | |
| 273 | pub const UnendingError = error{ | |
| 273 | 274 | InputOutput, |
| 274 | 275 | SystemResources, |
| 275 | 276 | /// Trying to read a directory file descriptor as if it were a file. |
| 276 | 277 | IsDir, |
| 277 | BrokenPipe, | |
| 278 | 278 | ConnectionResetByPeer, |
| 279 | 279 | /// File was not opened with read capability. |
| 280 | 280 | NotOpenForReading, |
lib/std/Io/File.zig+3-2| ... | ... | @@ -552,11 +552,13 @@ pub fn setTimestampsNow(file: File, io: Io) SetTimestampsError!void { |
| 552 | 552 | }); |
| 553 | 553 | } |
| 554 | 554 | |
| 555 | pub const ReadStreamingError = error{EndOfStream} || Reader.Error; | |
| 556 | ||
| 555 | 557 | /// Returns 0 on stream end or if `buffer` has no space available for data. |
| 556 | 558 | /// |
| 557 | 559 | /// See also: |
| 558 | 560 | /// * `reader` |
| 559 | pub fn readStreaming(file: File, io: Io, buffer: []const []u8) Reader.Error!usize { | |
| 561 | pub fn readStreaming(file: File, io: Io, buffer: []const []u8) ReadStreamingError!usize { | |
| 560 | 562 | var operation: Io.Operation = .{ .file_read_streaming = .{ |
| 561 | 563 | .file = file, |
| 562 | 564 | .data = buffer, |
| ... | ... | @@ -570,7 +572,6 @@ pub const ReadPositionalError = error{ |
| 570 | 572 | SystemResources, |
| 571 | 573 | /// Trying to read a directory file descriptor as if it were a file. |
| 572 | 574 | IsDir, |
| 573 | BrokenPipe, | |
| 574 | 575 | /// Non-blocking has been enabled, and reading from the file descriptor |
| 575 | 576 | /// would block. |
| 576 | 577 | WouldBlock, |
lib/std/Io/File/MultiReader.zig+5-9| ... | ... | @@ -15,10 +15,9 @@ pub const Context = struct { |
| 15 | 15 | fr: File.Reader, |
| 16 | 16 | vec: [1][]u8, |
| 17 | 17 | err: ?Error, |
| 18 | eos: bool, | |
| 19 | 18 | }; |
| 20 | 19 | |
| 21 | pub const Error = Allocator.Error || File.Reader.Error || Io.ConcurrentError; | |
| 20 | pub const Error = Allocator.Error || File.ReadStreamingError || Io.ConcurrentError; | |
| 22 | 21 | |
| 23 | 22 | /// Trailing: |
| 24 | 23 | /// * `contexts: [len]Context` |
| ... | ... | @@ -85,7 +84,6 @@ pub fn init(mr: *MultiReader, gpa: Allocator, io: Io, streams: *Streams, files: |
| 85 | 84 | }, |
| 86 | 85 | .vec = .{&.{}}, |
| 87 | 86 | .err = null, |
| 88 | .eos = false, | |
| 89 | 87 | }; |
| 90 | 88 | const operations = streams.operations(); |
| 91 | 89 | const ring = streams.ring(); |
| ... | ... | @@ -198,8 +196,10 @@ fn fillUntimed(context: *Context, capacity: usize) Io.Reader.Error!void { |
| 198 | 196 | }, |
| 199 | 197 | error.EndOfStream => |e| return e, |
| 200 | 198 | }; |
| 201 | if (context.err != null) return error.ReadFailed; | |
| 202 | if (context.eos) return error.EndOfStream; | |
| 199 | if (context.err) |err| switch (err) { | |
| 200 | error.EndOfStream => |e| return e, | |
| 201 | else => return error.ReadFailed, | |
| 202 | }; | |
| 203 | 203 | } |
| 204 | 204 | |
| 205 | 205 | pub const FillError = Io.Batch.WaitError || error{ |
| ... | ... | @@ -225,10 +225,6 @@ pub fn fill(mr: *MultiReader, unused_capacity: usize, timeout: Io.Timeout) FillE |
| 225 | 225 | context.err = err; |
| 226 | 226 | continue; |
| 227 | 227 | }; |
| 228 | if (n == 0) { | |
| 229 | context.eos = true; | |
| 230 | continue; | |
| 231 | } | |
| 232 | 228 | const r = &context.fr.interface; |
| 233 | 229 | r.end += n; |
| 234 | 230 | if (r.buffer.len - r.end < unused_capacity) { |
lib/std/Io/File/Reader.zig+19-15| ... | ... | @@ -26,7 +26,7 @@ size_err: ?SizeError = null, |
| 26 | 26 | seek_err: ?SeekError = null, |
| 27 | 27 | interface: Io.Reader, |
| 28 | 28 | |
| 29 | pub const Error = Io.Operation.FileReadStreaming.Error || Io.Cancelable; | |
| 29 | pub const Error = Io.Operation.FileReadStreaming.UnendingError || Io.Cancelable; | |
| 30 | 30 | |
| 31 | 31 | pub const SizeError = File.StatError || error{ |
| 32 | 32 | /// Occurs if, for example, the file handle is a network socket and therefore does not have a size. |
| ... | ... | @@ -280,14 +280,16 @@ fn readVecStreaming(r: *Reader, data: [][]u8) Io.Reader.Error!usize { |
| 280 | 280 | const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data); |
| 281 | 281 | const dest = iovecs_buffer[0..dest_n]; |
| 282 | 282 | assert(dest[0].len > 0); |
| 283 | const n = r.file.readStreaming(io, dest) catch |err| { | |
| 284 | r.err = err; | |
| 285 | return error.ReadFailed; | |
| 283 | const n = r.file.readStreaming(io, dest) catch |err| switch (err) { | |
| 284 | error.EndOfStream => { | |
| 285 | r.size = r.pos; | |
| 286 | return error.EndOfStream; | |
| 287 | }, | |
| 288 | else => |e| { | |
| 289 | r.err = e; | |
| 290 | return error.ReadFailed; | |
| 291 | }, | |
| 286 | 292 | }; |
| 287 | if (n == 0) { | |
| 288 | r.size = r.pos; | |
| 289 | return error.EndOfStream; | |
| 290 | } | |
| 291 | 293 | r.pos += n; |
| 292 | 294 | if (n > data_size) { |
| 293 | 295 | r.interface.end += n - data_size; |
| ... | ... | @@ -335,14 +337,16 @@ fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize { |
| 335 | 337 | const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, &data); |
| 336 | 338 | const dest = iovecs_buffer[0..dest_n]; |
| 337 | 339 | assert(dest[0].len > 0); |
| 338 | const n = file.readStreaming(io, dest) catch |err| { | |
| 339 | r.err = err; | |
| 340 | return error.ReadFailed; | |
| 340 | const n = file.readStreaming(io, dest) catch |err| switch (err) { | |
| 341 | error.EndOfStream => { | |
| 342 | r.size = r.pos; | |
| 343 | return error.EndOfStream; | |
| 344 | }, | |
| 345 | else => |e| { | |
| 346 | r.err = e; | |
| 347 | return error.ReadFailed; | |
| 348 | }, | |
| 341 | 349 | }; |
| 342 | if (n == 0) { | |
| 343 | r.size = r.pos; | |
| 344 | return error.EndOfStream; | |
| 345 | } | |
| 346 | 350 | r.pos += n; |
| 347 | 351 | if (n > data_size) { |
| 348 | 352 | r.interface.end += n - data_size; |
lib/std/Io/Threaded.zig+35-50| ... | ... | @@ -8583,14 +8583,14 @@ fn fileClose(userdata: ?*anyopaque, files: []const File) void { |
| 8583 | 8583 | for (files) |file| posix.close(file.handle); |
| 8584 | 8584 | } |
| 8585 | 8585 | |
| 8586 | fn fileReadStreaming(userdata: ?*anyopaque, file: File, data: []const []u8) File.Reader.Error!usize { | |
| 8586 | fn fileReadStreaming(userdata: ?*anyopaque, file: File, data: []const []u8) File.ReadStreamingError!usize { | |
| 8587 | 8587 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 8588 | 8588 | _ = t; |
| 8589 | 8589 | if (is_windows) return fileReadStreamingWindows(file, data); |
| 8590 | 8590 | return fileReadStreamingPosix(file, data); |
| 8591 | 8591 | } |
| 8592 | 8592 | |
| 8593 | fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usize { | |
| 8593 | fn fileReadStreamingPosix(file: File, data: []const []u8) File.ReadStreamingError!usize { | |
| 8594 | 8594 | var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined; |
| 8595 | 8595 | var i: usize = 0; |
| 8596 | 8596 | for (data) |buf| { |
| ... | ... | @@ -8611,28 +8611,24 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usiz |
| 8611 | 8611 | switch (std.os.wasi.fd_read(file.handle, dest.ptr, dest.len, &nread)) { |
| 8612 | 8612 | .SUCCESS => { |
| 8613 | 8613 | syscall.finish(); |
| 8614 | if (nread == 0) return error.EndOfStream; | |
| 8614 | 8615 | return nread; |
| 8615 | 8616 | }, |
| 8616 | 8617 | .INTR, .TIMEDOUT => { |
| 8617 | 8618 | try syscall.checkCancel(); |
| 8618 | 8619 | continue; |
| 8619 | 8620 | }, |
| 8620 | else => |e| { | |
| 8621 | syscall.finish(); | |
| 8622 | switch (e) { | |
| 8623 | .INVAL => |err| return errnoBug(err), | |
| 8624 | .FAULT => |err| return errnoBug(err), | |
| 8625 | .BADF => return error.IsDir, // File operation on directory. | |
| 8626 | .IO => return error.InputOutput, | |
| 8627 | .ISDIR => return error.IsDir, | |
| 8628 | .NOBUFS => return error.SystemResources, | |
| 8629 | .NOMEM => return error.SystemResources, | |
| 8630 | .NOTCONN => return error.SocketUnconnected, | |
| 8631 | .CONNRESET => return error.ConnectionResetByPeer, | |
| 8632 | .NOTCAPABLE => return error.AccessDenied, | |
| 8633 | else => |err| return posix.unexpectedErrno(err), | |
| 8634 | } | |
| 8635 | }, | |
| 8621 | .BADF => return syscall.fail(error.IsDir), // File operation on directory. | |
| 8622 | .IO => return syscall.fail(error.InputOutput), | |
| 8623 | .ISDIR => return syscall.fail(error.IsDir), | |
| 8624 | .NOBUFS => return syscall.fail(error.SystemResources), | |
| 8625 | .NOMEM => return syscall.fail(error.SystemResources), | |
| 8626 | .NOTCONN => return syscall.fail(error.SocketUnconnected), | |
| 8627 | .CONNRESET => return syscall.fail(error.ConnectionResetByPeer), | |
| 8628 | .NOTCAPABLE => return syscall.fail(error.AccessDenied), | |
| 8629 | .INVAL => |err| return syscall.errnoBug(err), | |
| 8630 | .FAULT => |err| return syscall.errnoBug(err), | |
| 8631 | else => |err| return syscall.unexpectedErrno(err), | |
| 8636 | 8632 | } |
| 8637 | 8633 | } |
| 8638 | 8634 | } |
| ... | ... | @@ -8643,36 +8639,33 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usiz |
| 8643 | 8639 | switch (posix.errno(rc)) { |
| 8644 | 8640 | .SUCCESS => { |
| 8645 | 8641 | syscall.finish(); |
| 8642 | if (rc == 0) return error.EndOfStream; | |
| 8646 | 8643 | return @intCast(rc); |
| 8647 | 8644 | }, |
| 8648 | 8645 | .INTR, .TIMEDOUT => { |
| 8649 | 8646 | try syscall.checkCancel(); |
| 8650 | 8647 | continue; |
| 8651 | 8648 | }, |
| 8652 | else => |e| { | |
| 8649 | .BADF => { | |
| 8653 | 8650 | syscall.finish(); |
| 8654 | switch (e) { | |
| 8655 | .INVAL => |err| return errnoBug(err), | |
| 8656 | .FAULT => |err| return errnoBug(err), | |
| 8657 | .AGAIN => return error.WouldBlock, | |
| 8658 | .BADF => { | |
| 8659 | if (native_os == .wasi) return error.IsDir; // File operation on directory. | |
| 8660 | return error.NotOpenForReading; | |
| 8661 | }, | |
| 8662 | .IO => return error.InputOutput, | |
| 8663 | .ISDIR => return error.IsDir, | |
| 8664 | .NOBUFS => return error.SystemResources, | |
| 8665 | .NOMEM => return error.SystemResources, | |
| 8666 | .NOTCONN => return error.SocketUnconnected, | |
| 8667 | .CONNRESET => return error.ConnectionResetByPeer, | |
| 8668 | else => |err| return posix.unexpectedErrno(err), | |
| 8669 | } | |
| 8651 | if (native_os == .wasi) return error.IsDir; // File operation on directory. | |
| 8652 | return error.NotOpenForReading; | |
| 8670 | 8653 | }, |
| 8654 | .AGAIN => return syscall.fail(error.WouldBlock), | |
| 8655 | .IO => return syscall.fail(error.InputOutput), | |
| 8656 | .ISDIR => return syscall.fail(error.IsDir), | |
| 8657 | .NOBUFS => return syscall.fail(error.SystemResources), | |
| 8658 | .NOMEM => return syscall.fail(error.SystemResources), | |
| 8659 | .NOTCONN => return syscall.fail(error.SocketUnconnected), | |
| 8660 | .CONNRESET => return syscall.fail(error.ConnectionResetByPeer), | |
| 8661 | .INVAL => |err| return syscall.errnoBug(err), | |
| 8662 | .FAULT => |err| return syscall.errnoBug(err), | |
| 8663 | else => |err| return syscall.unexpectedErrno(err), | |
| 8671 | 8664 | } |
| 8672 | 8665 | } |
| 8673 | 8666 | } |
| 8674 | 8667 | |
| 8675 | fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!usize { | |
| 8668 | fn fileReadStreamingWindows(file: File, data: []const []u8) File.ReadStreamingError!usize { | |
| 8676 | 8669 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 8677 | 8670 | if (ntReadFile(file.handle, data, &io_status_block)) |result| switch (result) { |
| 8678 | 8671 | .status => return ntReadFileResult(&io_status_block), |
| ... | ... | @@ -8707,11 +8700,9 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us |
| 8707 | 8700 | |
| 8708 | 8701 | fn ntReadFileResult(io_status_block: *windows.IO_STATUS_BLOCK) !usize { |
| 8709 | 8702 | switch (io_status_block.u.Status) { |
| 8710 | .SUCCESS => { | |
| 8711 | assert(io_status_block.Information != 0); | |
| 8712 | return io_status_block.Information; | |
| 8713 | }, | |
| 8714 | .END_OF_FILE, .PIPE_BROKEN => return 0, | |
| 8703 | .SUCCESS => return io_status_block.Information, | |
| 8704 | .END_OF_FILE => return error.EndOfStream, | |
| 8705 | .PIPE_BROKEN => return error.EndOfStream, | |
| 8715 | 8706 | .PENDING => unreachable, |
| 8716 | 8707 | .INVALID_DEVICE_REQUEST => return error.IsDir, |
| 8717 | 8708 | .LOCK_NOT_GRANTED => return error.LockViolation, |
| ... | ... | @@ -8749,15 +8740,9 @@ fn ntReadFile(handle: windows.HANDLE, data: []const []u8, iosb: *windows.IO_STAT |
| 8749 | 8740 | return .pending; |
| 8750 | 8741 | }, |
| 8751 | 8742 | .SUCCESS => { |
| 8752 | // Only END_OF_FILE is the true end. | |
| 8753 | if (iosb.Information == 0) { | |
| 8754 | try syscall.checkCancel(); | |
| 8755 | continue; | |
| 8756 | } else { | |
| 8757 | syscall.finish(); | |
| 8758 | iosb.u.Status = .SUCCESS; | |
| 8759 | return .status; | |
| 8760 | } | |
| 8743 | syscall.finish(); | |
| 8744 | iosb.u.Status = .SUCCESS; | |
| 8745 | return .status; | |
| 8761 | 8746 | }, |
| 8762 | 8747 | .CANCELLED => { |
| 8763 | 8748 | try syscall.checkCancel(); |
lib/std/Progress.zig+1-2| ... | ... | @@ -984,7 +984,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff |
| 984 | 984 | var bytes_read: usize = 0; |
| 985 | 985 | while (true) { |
| 986 | 986 | const n = file.readStreaming(io, &.{pipe_buf[bytes_read..]}) catch |err| switch (err) { |
| 987 | error.WouldBlock => break, | |
| 987 | error.WouldBlock, error.EndOfStream => break, | |
| 988 | 988 | else => |e| { |
| 989 | 989 | std.log.debug("failed to read child progress data: {t}", .{e}); |
| 990 | 990 | main_storage.completed_count = 0; |
| ... | ... | @@ -992,7 +992,6 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff |
| 992 | 992 | continue :main_loop; |
| 993 | 993 | }, |
| 994 | 994 | }; |
| 995 | if (n == 0) break; | |
| 996 | 995 | if (opt_saved_metadata) |m| { |
| 997 | 996 | if (m.remaining_read_trash_bytes > 0) { |
| 998 | 997 | assert(bytes_read == 0); |
lib/std/process/Child.zig+15-13| ... | ... | @@ -176,19 +176,21 @@ pub fn collectOutput(child: *const Child, io: Io, options: CollectOutputOptions) |
| 176 | 176 | while (remaining > 0) { |
| 177 | 177 | try batch.wait(io, options.timeout); |
| 178 | 178 | while (batch.next()) |op| { |
| 179 | const n = try reads[op].file_read_streaming.status.result; | |
| 180 | if (n == 0) { | |
| 181 | remaining -= 1; | |
| 182 | } else { | |
| 183 | lists[op].items.len += n; | |
| 184 | if (lists[op].items.len > @intFromEnum(limits[op])) return error.StreamTooLong; | |
| 185 | if (options.allocator) |gpa| try lists[op].ensureUnusedCapacity(gpa, 1); | |
| 186 | const cap = lists[op].unusedCapacitySlice(); | |
| 187 | if (cap.len == 0) return error.StreamTooLong; | |
| 188 | vecs[op][0] = cap; | |
| 189 | reads[op].file_read_streaming.status = .{ .unstarted = {} }; | |
| 190 | batch.add(op); | |
| 191 | } | |
| 179 | const n = reads[op].file_read_streaming.status.result catch |err| switch (err) { | |
| 180 | error.EndOfStream => { | |
| 181 | remaining -= 1; | |
| 182 | continue; | |
| 183 | }, | |
| 184 | else => |e| return e, | |
| 185 | }; | |
| 186 | lists[op].items.len += n; | |
| 187 | if (lists[op].items.len > @intFromEnum(limits[op])) return error.StreamTooLong; | |
| 188 | if (options.allocator) |gpa| try lists[op].ensureUnusedCapacity(gpa, 1); | |
| 189 | const cap = lists[op].unusedCapacitySlice(); | |
| 190 | if (cap.len == 0) return error.StreamTooLong; | |
| 191 | vecs[op][0] = cap; | |
| 192 | reads[op].file_read_streaming.status = .{ .unstarted = {} }; | |
| 193 | batch.add(op); | |
| 192 | 194 | } |
| 193 | 195 | } |
| 194 | 196 | } |
lib/std/zig/system.zig-1| ... | ... | @@ -420,7 +420,6 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target { |
| 420 | 420 | error.Canceled => |e| return e, |
| 421 | 421 | error.Unexpected => |e| return e, |
| 422 | 422 | error.WouldBlock => return error.Unexpected, |
| 423 | error.BrokenPipe => return error.Unexpected, | |
| 424 | 423 | error.ConnectionResetByPeer => return error.Unexpected, |
| 425 | 424 | error.NotOpenForReading => return error.Unexpected, |
| 426 | 425 | error.SocketUnconnected => return error.Unexpected, |