authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-27 15:31:23-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-30 22:03:14-08:00
log8a80b5464022a9fb4320e37f9dfc2aa83539ff07
tree0fd5f5d40f3e9d1f600622f1c3554e95c7129aed
parentfdf1ee973e9f3cb01f6fec9e460950622cdf92e4

std: remove error.BrokenPipe from file reads, add error.EndOfStream

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 {
187187 fileWritePositional: *const fn (?*anyopaque, File, header: []const u8, data: []const []const u8, splat: usize, offset: u64) File.WritePositionalError!usize,
188188 fileWriteFileStreaming: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit) File.Writer.WriteFileError!usize,
189189 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.
191191 fileReadPositional: *const fn (?*anyopaque, File, data: []const []u8, offset: u64) File.ReadPositionalError!usize,
192192 fileSeekBy: *const fn (?*anyopaque, File, relative_offset: i64) File.SeekError!void,
193193 fileSeekTo: *const fn (?*anyopaque, File, absolute_offset: u64) File.SeekError!void,
......@@ -263,18 +263,18 @@ pub const Operation = union(enum) {
263263 status: Status(void) = .{ .unstarted = {} },
264264 };
265265
266 /// Returns 0 on end of stream.
266 /// May return 0 reads which is different than `error.EndOfStream`.
267267 pub const FileReadStreaming = struct {
268268 file: File,
269269 data: []const []u8,
270270 status: Status(Error!usize) = .{ .unstarted = {} },
271271
272 pub const Error = error{
272 pub const Error = UnendingError || error{EndOfStream};
273 pub const UnendingError = error{
273274 InputOutput,
274275 SystemResources,
275276 /// Trying to read a directory file descriptor as if it were a file.
276277 IsDir,
277 BrokenPipe,
278278 ConnectionResetByPeer,
279279 /// File was not opened with read capability.
280280 NotOpenForReading,
lib/std/Io/File.zig+3-2
......@@ -552,11 +552,13 @@ pub fn setTimestampsNow(file: File, io: Io) SetTimestampsError!void {
552552 });
553553}
554554
555pub const ReadStreamingError = error{EndOfStream} || Reader.Error;
556
555557/// Returns 0 on stream end or if `buffer` has no space available for data.
556558///
557559/// See also:
558560/// * `reader`
559pub fn readStreaming(file: File, io: Io, buffer: []const []u8) Reader.Error!usize {
561pub fn readStreaming(file: File, io: Io, buffer: []const []u8) ReadStreamingError!usize {
560562 var operation: Io.Operation = .{ .file_read_streaming = .{
561563 .file = file,
562564 .data = buffer,
......@@ -570,7 +572,6 @@ pub const ReadPositionalError = error{
570572 SystemResources,
571573 /// Trying to read a directory file descriptor as if it were a file.
572574 IsDir,
573 BrokenPipe,
574575 /// Non-blocking has been enabled, and reading from the file descriptor
575576 /// would block.
576577 WouldBlock,
lib/std/Io/File/MultiReader.zig+5-9
......@@ -15,10 +15,9 @@ pub const Context = struct {
1515 fr: File.Reader,
1616 vec: [1][]u8,
1717 err: ?Error,
18 eos: bool,
1918};
2019
21pub const Error = Allocator.Error || File.Reader.Error || Io.ConcurrentError;
20pub const Error = Allocator.Error || File.ReadStreamingError || Io.ConcurrentError;
2221
2322/// Trailing:
2423/// * `contexts: [len]Context`
......@@ -85,7 +84,6 @@ pub fn init(mr: *MultiReader, gpa: Allocator, io: Io, streams: *Streams, files:
8584 },
8685 .vec = .{&.{}},
8786 .err = null,
88 .eos = false,
8987 };
9088 const operations = streams.operations();
9189 const ring = streams.ring();
......@@ -198,8 +196,10 @@ fn fillUntimed(context: *Context, capacity: usize) Io.Reader.Error!void {
198196 },
199197 error.EndOfStream => |e| return e,
200198 };
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 };
203203}
204204
205205pub const FillError = Io.Batch.WaitError || error{
......@@ -225,10 +225,6 @@ pub fn fill(mr: *MultiReader, unused_capacity: usize, timeout: Io.Timeout) FillE
225225 context.err = err;
226226 continue;
227227 };
228 if (n == 0) {
229 context.eos = true;
230 continue;
231 }
232228 const r = &context.fr.interface;
233229 r.end += n;
234230 if (r.buffer.len - r.end < unused_capacity) {
lib/std/Io/File/Reader.zig+19-15
......@@ -26,7 +26,7 @@ size_err: ?SizeError = null,
2626seek_err: ?SeekError = null,
2727interface: Io.Reader,
2828
29pub const Error = Io.Operation.FileReadStreaming.Error || Io.Cancelable;
29pub const Error = Io.Operation.FileReadStreaming.UnendingError || Io.Cancelable;
3030
3131pub const SizeError = File.StatError || error{
3232 /// 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 {
280280 const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data);
281281 const dest = iovecs_buffer[0..dest_n];
282282 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 },
286292 };
287 if (n == 0) {
288 r.size = r.pos;
289 return error.EndOfStream;
290 }
291293 r.pos += n;
292294 if (n > data_size) {
293295 r.interface.end += n - data_size;
......@@ -335,14 +337,16 @@ fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
335337 const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, &data);
336338 const dest = iovecs_buffer[0..dest_n];
337339 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 },
341349 };
342 if (n == 0) {
343 r.size = r.pos;
344 return error.EndOfStream;
345 }
346350 r.pos += n;
347351 if (n > data_size) {
348352 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 {
85838583 for (files) |file| posix.close(file.handle);
85848584}
85858585
8586fn fileReadStreaming(userdata: ?*anyopaque, file: File, data: []const []u8) File.Reader.Error!usize {
8586fn fileReadStreaming(userdata: ?*anyopaque, file: File, data: []const []u8) File.ReadStreamingError!usize {
85878587 const t: *Threaded = @ptrCast(@alignCast(userdata));
85888588 _ = t;
85898589 if (is_windows) return fileReadStreamingWindows(file, data);
85908590 return fileReadStreamingPosix(file, data);
85918591}
85928592
8593fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usize {
8593fn fileReadStreamingPosix(file: File, data: []const []u8) File.ReadStreamingError!usize {
85948594 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
85958595 var i: usize = 0;
85968596 for (data) |buf| {
......@@ -8611,28 +8611,24 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usiz
86118611 switch (std.os.wasi.fd_read(file.handle, dest.ptr, dest.len, &nread)) {
86128612 .SUCCESS => {
86138613 syscall.finish();
8614 if (nread == 0) return error.EndOfStream;
86148615 return nread;
86158616 },
86168617 .INTR, .TIMEDOUT => {
86178618 try syscall.checkCancel();
86188619 continue;
86198620 },
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),
86368632 }
86378633 }
86388634 }
......@@ -8643,36 +8639,33 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usiz
86438639 switch (posix.errno(rc)) {
86448640 .SUCCESS => {
86458641 syscall.finish();
8642 if (rc == 0) return error.EndOfStream;
86468643 return @intCast(rc);
86478644 },
86488645 .INTR, .TIMEDOUT => {
86498646 try syscall.checkCancel();
86508647 continue;
86518648 },
8652 else => |e| {
8649 .BADF => {
86538650 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;
86708653 },
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),
86718664 }
86728665 }
86738666}
86748667
8675fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!usize {
8668fn fileReadStreamingWindows(file: File, data: []const []u8) File.ReadStreamingError!usize {
86768669 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
86778670 if (ntReadFile(file.handle, data, &io_status_block)) |result| switch (result) {
86788671 .status => return ntReadFileResult(&io_status_block),
......@@ -8707,11 +8700,9 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us
87078700
87088701fn ntReadFileResult(io_status_block: *windows.IO_STATUS_BLOCK) !usize {
87098702 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,
87158706 .PENDING => unreachable,
87168707 .INVALID_DEVICE_REQUEST => return error.IsDir,
87178708 .LOCK_NOT_GRANTED => return error.LockViolation,
......@@ -8749,15 +8740,9 @@ fn ntReadFile(handle: windows.HANDLE, data: []const []u8, iosb: *windows.IO_STAT
87498740 return .pending;
87508741 },
87518742 .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;
87618746 },
87628747 .CANCELLED => {
87638748 try syscall.checkCancel();
lib/std/Progress.zig+1-2
......@@ -984,7 +984,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff
984984 var bytes_read: usize = 0;
985985 while (true) {
986986 const n = file.readStreaming(io, &.{pipe_buf[bytes_read..]}) catch |err| switch (err) {
987 error.WouldBlock => break,
987 error.WouldBlock, error.EndOfStream => break,
988988 else => |e| {
989989 std.log.debug("failed to read child progress data: {t}", .{e});
990990 main_storage.completed_count = 0;
......@@ -992,7 +992,6 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff
992992 continue :main_loop;
993993 },
994994 };
995 if (n == 0) break;
996995 if (opt_saved_metadata) |m| {
997996 if (m.remaining_read_trash_bytes > 0) {
998997 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)
176176 while (remaining > 0) {
177177 try batch.wait(io, options.timeout);
178178 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);
192194 }
193195 }
194196}
lib/std/zig/system.zig-1
......@@ -420,7 +420,6 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target {
420420 error.Canceled => |e| return e,
421421 error.Unexpected => |e| return e,
422422 error.WouldBlock => return error.Unexpected,
423 error.BrokenPipe => return error.Unexpected,
424423 error.ConnectionResetByPeer => return error.Unexpected,
425424 error.NotOpenForReading => return error.Unexpected,
426425 error.SocketUnconnected => return error.Unexpected,