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-27 15:33:46-08:00
logf612d5ae96c9f327e9cb5e53d8e47db7ba206418
tree6431826dcf2a83d44ee6059de783ca926f39b983
parent46b5eb6ec3fed3c97512c05c13aa6f3dd9b71b87

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.

6 files changed, 62 insertions(+), 75 deletions(-)

lib/std/Io.zig+4-7
...@@ -264,10 +264,7 @@ pub fn Poller(comptime StreamEnum: type) type {...@@ -264,10 +264,7 @@ pub fn Poller(comptime StreamEnum: type) type {
264 // always check if there's some data waiting to be read first.264 // always check if there's some data waiting to be read first.
265 if (poll_fd.revents & posix.POLL.IN != 0) {265 if (poll_fd.revents & posix.POLL.IN != 0) {
266 const buf = try writableSliceGreedyAlloc(r, gpa, bump_amt);266 const buf = try writableSliceGreedyAlloc(r, gpa, bump_amt);
267 const amt = posix.read(poll_fd.fd, buf) catch |err| switch (err) {267 const amt = try posix.read(poll_fd.fd, buf);
268 error.BrokenPipe => 0, // Handle the same as EOF.
269 else => |e| return e,
270 };
271 advanceBufferEnd(r, amt);268 advanceBufferEnd(r, amt);
272 if (amt == 0) {269 if (amt == 0) {
273 // Remove the fd when the EOF condition is met.270 // Remove the fd when the EOF condition is met.
...@@ -633,9 +630,9 @@ pub const VTable = struct {...@@ -633,9 +630,9 @@ pub const VTable = struct {
633 fileWritePositional: *const fn (?*anyopaque, File, header: []const u8, data: []const []const u8, splat: usize, offset: u64) File.WritePositionalError!usize,630 fileWritePositional: *const fn (?*anyopaque, File, header: []const u8, data: []const []const u8, splat: usize, offset: u64) File.WritePositionalError!usize,
634 fileWriteFileStreaming: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit) File.Writer.WriteFileError!usize,631 fileWriteFileStreaming: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit) File.Writer.WriteFileError!usize,
635 fileWriteFilePositional: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit, offset: u64) File.WriteFilePositionalError!usize,632 fileWriteFilePositional: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit, offset: u64) File.WriteFilePositionalError!usize,
636 /// Returns 0 on end of stream.633 /// May return 0 reads which is different than `error.EndOfStream`.
637 fileReadStreaming: *const fn (?*anyopaque, File, data: []const []u8) File.Reader.Error!usize,634 fileReadStreaming: *const fn (?*anyopaque, File, data: []const []u8) File.ReadStreamingError!usize,
638 /// Returns 0 on end of stream.635 /// Returns 0 if reading at or past the end.
639 fileReadPositional: *const fn (?*anyopaque, File, data: []const []u8, offset: u64) File.ReadPositionalError!usize,636 fileReadPositional: *const fn (?*anyopaque, File, data: []const []u8, offset: u64) File.ReadPositionalError!usize,
640 fileSeekBy: *const fn (?*anyopaque, File, relative_offset: i64) File.SeekError!void,637 fileSeekBy: *const fn (?*anyopaque, File, relative_offset: i64) File.SeekError!void,
641 fileSeekTo: *const fn (?*anyopaque, File, absolute_offset: u64) File.SeekError!void,638 fileSeekTo: *const fn (?*anyopaque, File, absolute_offset: u64) File.SeekError!void,
lib/std/Io/File.zig+3-2
...@@ -550,11 +550,13 @@ pub fn setTimestampsNow(file: File, io: Io) SetTimestampsError!void {...@@ -550,11 +550,13 @@ pub fn setTimestampsNow(file: File, io: Io) SetTimestampsError!void {
550 });550 });
551}551}
552552
553pub const ReadStreamingError = error{EndOfStream} || Reader.Error;
554
553/// Returns 0 on stream end or if `buffer` has no space available for data.555/// Returns 0 on stream end or if `buffer` has no space available for data.
554///556///
555/// See also:557/// See also:
556/// * `reader`558/// * `reader`
557pub fn readStreaming(file: File, io: Io, buffer: []const []u8) Reader.Error!usize {559pub fn readStreaming(file: File, io: Io, buffer: []const []u8) ReadStreamingError!usize {
558 return io.vtable.fileReadStreaming(io.userdata, file, buffer);560 return io.vtable.fileReadStreaming(io.userdata, file, buffer);
559}561}
560562
...@@ -563,7 +565,6 @@ pub const ReadPositionalError = error{...@@ -563,7 +565,6 @@ pub const ReadPositionalError = error{
563 SystemResources,565 SystemResources,
564 /// Trying to read a directory file descriptor as if it were a file.566 /// Trying to read a directory file descriptor as if it were a file.
565 IsDir,567 IsDir,
566 BrokenPipe,
567 /// Non-blocking has been enabled, and reading from the file descriptor568 /// Non-blocking has been enabled, and reading from the file descriptor
568 /// would block.569 /// would block.
569 WouldBlock,570 WouldBlock,
lib/std/Io/File/Reader.zig+18-15
...@@ -31,7 +31,6 @@ pub const Error = error{...@@ -31,7 +31,6 @@ pub const Error = error{
31 SystemResources,31 SystemResources,
32 /// Trying to read a directory file descriptor as if it were a file.32 /// Trying to read a directory file descriptor as if it were a file.
33 IsDir,33 IsDir,
34 BrokenPipe,
35 ConnectionResetByPeer,34 ConnectionResetByPeer,
36 /// File was not opened with read capability.35 /// File was not opened with read capability.
37 NotOpenForReading,36 NotOpenForReading,
...@@ -300,14 +299,16 @@ fn readVecStreaming(r: *Reader, data: [][]u8) Io.Reader.Error!usize {...@@ -300,14 +299,16 @@ fn readVecStreaming(r: *Reader, data: [][]u8) Io.Reader.Error!usize {
300 const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data);299 const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data);
301 const dest = iovecs_buffer[0..dest_n];300 const dest = iovecs_buffer[0..dest_n];
302 assert(dest[0].len > 0);301 assert(dest[0].len > 0);
303 const n = io.vtable.fileReadStreaming(io.userdata, r.file, dest) catch |err| {302 const n = io.vtable.fileReadStreaming(io.userdata, r.file, dest) catch |err| switch (err) {
304 r.err = err;303 error.EndOfStream => {
305 return error.ReadFailed;304 r.size = r.pos;
305 return error.EndOfStream;
306 },
307 else => |e| {
308 r.err = e;
309 return error.ReadFailed;
310 },
306 };311 };
307 if (n == 0) {
308 r.size = r.pos;
309 return error.EndOfStream;
310 }
311 r.pos += n;312 r.pos += n;
312 if (n > data_size) {313 if (n > data_size) {
313 r.interface.end += n - data_size;314 r.interface.end += n - data_size;
...@@ -355,14 +356,16 @@ fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {...@@ -355,14 +356,16 @@ fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
355 const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, &data);356 const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, &data);
356 const dest = iovecs_buffer[0..dest_n];357 const dest = iovecs_buffer[0..dest_n];
357 assert(dest[0].len > 0);358 assert(dest[0].len > 0);
358 const n = io.vtable.fileReadStreaming(io.userdata, file, dest) catch |err| {359 const n = io.vtable.fileReadStreaming(io.userdata, file, dest) catch |err| switch (err) {
359 r.err = err;360 error.EndOfStream => {
360 return error.ReadFailed;361 r.size = r.pos;
362 return error.EndOfStream;
363 },
364 else => |e| {
365 r.err = e;
366 return error.ReadFailed;
367 },
361 };368 };
362 if (n == 0) {
363 r.size = r.pos;
364 return error.EndOfStream;
365 }
366 r.pos += n;369 r.pos += n;
367 if (n > data_size) {370 if (n > data_size) {
368 r.interface.end += n - data_size;371 r.interface.end += n - data_size;
lib/std/Io/Threaded.zig+36-48
...@@ -8139,14 +8139,14 @@ fn fileClose(userdata: ?*anyopaque, files: []const File) void {...@@ -8139,14 +8139,14 @@ fn fileClose(userdata: ?*anyopaque, files: []const File) void {
8139 for (files) |file| posix.close(file.handle);8139 for (files) |file| posix.close(file.handle);
8140}8140}
81418141
8142fn fileReadStreaming(userdata: ?*anyopaque, file: File, data: []const []u8) File.Reader.Error!usize {8142fn fileReadStreaming(userdata: ?*anyopaque, file: File, data: []const []u8) File.ReadStreamingError!usize {
8143 const t: *Threaded = @ptrCast(@alignCast(userdata));8143 const t: *Threaded = @ptrCast(@alignCast(userdata));
8144 _ = t;8144 _ = t;
8145 if (is_windows) return fileReadStreamingWindows(file, data);8145 if (is_windows) return fileReadStreamingWindows(file, data);
8146 return fileReadStreamingPosix(file, data);8146 return fileReadStreamingPosix(file, data);
8147}8147}
81488148
8149fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usize {8149fn fileReadStreamingPosix(file: File, data: []const []u8) File.ReadStreamingError!usize {
8150 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;8150 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
8151 var i: usize = 0;8151 var i: usize = 0;
8152 for (data) |buf| {8152 for (data) |buf| {
...@@ -8167,28 +8167,24 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usiz...@@ -8167,28 +8167,24 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usiz
8167 switch (std.os.wasi.fd_read(file.handle, dest.ptr, dest.len, &nread)) {8167 switch (std.os.wasi.fd_read(file.handle, dest.ptr, dest.len, &nread)) {
8168 .SUCCESS => {8168 .SUCCESS => {
8169 syscall.finish();8169 syscall.finish();
8170 if (nread == 0) return error.EndOfStream;
8170 return nread;8171 return nread;
8171 },8172 },
8172 .INTR, .TIMEDOUT => {8173 .INTR, .TIMEDOUT => {
8173 try syscall.checkCancel();8174 try syscall.checkCancel();
8174 continue;8175 continue;
8175 },8176 },
8176 else => |e| {8177 .BADF => return syscall.fail(error.IsDir), // File operation on directory.
8177 syscall.finish();8178 .IO => return syscall.fail(error.InputOutput),
8178 switch (e) {8179 .ISDIR => return syscall.fail(error.IsDir),
8179 .INVAL => |err| return errnoBug(err),8180 .NOBUFS => return syscall.fail(error.SystemResources),
8180 .FAULT => |err| return errnoBug(err),8181 .NOMEM => return syscall.fail(error.SystemResources),
8181 .BADF => return error.IsDir, // File operation on directory.8182 .NOTCONN => return syscall.fail(error.SocketUnconnected),
8182 .IO => return error.InputOutput,8183 .CONNRESET => return syscall.fail(error.ConnectionResetByPeer),
8183 .ISDIR => return error.IsDir,8184 .NOTCAPABLE => return syscall.fail(error.AccessDenied),
8184 .NOBUFS => return error.SystemResources,8185 .INVAL => |err| return syscall.errnoBug(err),
8185 .NOMEM => return error.SystemResources,8186 .FAULT => |err| return syscall.errnoBug(err),
8186 .NOTCONN => return error.SocketUnconnected,8187 else => |err| return syscall.unexpectedErrno(err),
8187 .CONNRESET => return error.ConnectionResetByPeer,
8188 .NOTCAPABLE => return error.AccessDenied,
8189 else => |err| return posix.unexpectedErrno(err),
8190 }
8191 },
8192 }8188 }
8193 }8189 }
8194 }8190 }
...@@ -8199,36 +8195,33 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usiz...@@ -8199,36 +8195,33 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usiz
8199 switch (posix.errno(rc)) {8195 switch (posix.errno(rc)) {
8200 .SUCCESS => {8196 .SUCCESS => {
8201 syscall.finish();8197 syscall.finish();
8198 if (rc == 0) return error.EndOfStream;
8202 return @intCast(rc);8199 return @intCast(rc);
8203 },8200 },
8204 .INTR, .TIMEDOUT => {8201 .INTR, .TIMEDOUT => {
8205 try syscall.checkCancel();8202 try syscall.checkCancel();
8206 continue;8203 continue;
8207 },8204 },
8208 else => |e| {8205 .BADF => {
8209 syscall.finish();8206 syscall.finish();
8210 switch (e) {8207 if (native_os == .wasi) return error.IsDir; // File operation on directory.
8211 .INVAL => |err| return errnoBug(err),8208 return error.NotOpenForReading;
8212 .FAULT => |err| return errnoBug(err),
8213 .AGAIN => return error.WouldBlock,
8214 .BADF => {
8215 if (native_os == .wasi) return error.IsDir; // File operation on directory.
8216 return error.NotOpenForReading;
8217 },
8218 .IO => return error.InputOutput,
8219 .ISDIR => return error.IsDir,
8220 .NOBUFS => return error.SystemResources,
8221 .NOMEM => return error.SystemResources,
8222 .NOTCONN => return error.SocketUnconnected,
8223 .CONNRESET => return error.ConnectionResetByPeer,
8224 else => |err| return posix.unexpectedErrno(err),
8225 }
8226 },8209 },
8210 .AGAIN => return syscall.fail(error.WouldBlock),
8211 .IO => return syscall.fail(error.InputOutput),
8212 .ISDIR => return syscall.fail(error.IsDir),
8213 .NOBUFS => return syscall.fail(error.SystemResources),
8214 .NOMEM => return syscall.fail(error.SystemResources),
8215 .NOTCONN => return syscall.fail(error.SocketUnconnected),
8216 .CONNRESET => return syscall.fail(error.ConnectionResetByPeer),
8217 .INVAL => |err| return syscall.errnoBug(err),
8218 .FAULT => |err| return syscall.errnoBug(err),
8219 else => |err| return syscall.unexpectedErrno(err),
8227 }8220 }
8228 }8221 }
8229}8222}
82308223
8231fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!usize {8224fn fileReadStreamingWindows(file: File, data: []const []u8) File.ReadStreamingError!usize {
8232 var index: usize = 0;8225 var index: usize = 0;
8233 while (index < data.len and data[index].len == 0) index += 1;8226 while (index < data.len and data[index].len == 0) index += 1;
8234 if (index == data.len) return 0;8227 if (index == data.len) return 0;
...@@ -8250,25 +8243,18 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us...@@ -8250,25 +8243,18 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us
8250 null, // key8243 null, // key
8251 )) {8244 )) {
8252 .SUCCESS => {8245 .SUCCESS => {
8253 // Only END_OF_FILE is the true end.8246 syscall.finish();
8254 if (io_status_block.Information == 0) {8247 return io_status_block.Information;
8255 try syscall.checkCancel();
8256 continue;
8257 } else {
8258 syscall.finish();
8259 io_status_block.u.Status = .SUCCESS;
8260 return io_status_block.Information;
8261 }
8262 },8248 },
8263 .END_OF_FILE, .PIPE_BROKEN => {8249 .END_OF_FILE, .PIPE_BROKEN => {
8264 syscall.finish();8250 syscall.finish();
8265 return io_status_block.Information;8251 return error.EndOfStream;
8266 },8252 },
8267 .PENDING => break,
8268 .CANCELLED => {8253 .CANCELLED => {
8269 try syscall.checkCancel();8254 try syscall.checkCancel();
8270 continue;8255 continue;
8271 },8256 },
8257 .PENDING => break,
8272 .INVALID_DEVICE_REQUEST => return syscall.fail(error.IsDir),8258 .INVALID_DEVICE_REQUEST => return syscall.fail(error.IsDir),
8273 .LOCK_NOT_GRANTED => return syscall.fail(error.LockViolation),8259 .LOCK_NOT_GRANTED => return syscall.fail(error.LockViolation),
8274 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),8260 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
...@@ -8302,7 +8288,9 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us...@@ -8302,7 +8288,9 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us
8302 alertable_syscall.finish();8288 alertable_syscall.finish();
8303 }8289 }
8304 switch (io_status_block.u.Status) {8290 switch (io_status_block.u.Status) {
8305 .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => return io_status_block.Information,8291 .SUCCESS => return io_status_block.Information,
8292 .END_OF_FILE => return error.EndOfStream,
8293 .PIPE_BROKEN => return error.EndOfStream,
8306 .PENDING => unreachable, // cannot return until the operation completes8294 .PENDING => unreachable, // cannot return until the operation completes
8307 .INVALID_DEVICE_REQUEST => return error.IsDir,8295 .INVALID_DEVICE_REQUEST => return error.IsDir,
8308 .LOCK_NOT_GRANTED => return error.LockViolation,8296 .LOCK_NOT_GRANTED => return error.LockViolation,
lib/std/Progress.zig+1-2
...@@ -984,7 +984,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff...@@ -984,7 +984,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff
984 var bytes_read: usize = 0;984 var bytes_read: usize = 0;
985 while (true) {985 while (true) {
986 const n = file.readStreaming(io, &.{pipe_buf[bytes_read..]}) catch |err| switch (err) {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 else => |e| {988 else => |e| {
989 std.log.debug("failed to read child progress data: {t}", .{e});989 std.log.debug("failed to read child progress data: {t}", .{e});
990 main_storage.completed_count = 0;990 main_storage.completed_count = 0;
...@@ -992,7 +992,6 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff...@@ -992,7 +992,6 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff
992 continue :main_loop;992 continue :main_loop;
993 },993 },
994 };994 };
995 if (n == 0) break;
996 if (opt_saved_metadata) |m| {995 if (opt_saved_metadata) |m| {
997 if (m.remaining_read_trash_bytes > 0) {996 if (m.remaining_read_trash_bytes > 0) {
998 assert(bytes_read == 0);997 assert(bytes_read == 0);
lib/std/zig/system.zig-1
...@@ -418,7 +418,6 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target {...@@ -418,7 +418,6 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target {
418 error.Canceled => |e| return e,418 error.Canceled => |e| return e,
419 error.Unexpected => |e| return e,419 error.Unexpected => |e| return e,
420 error.WouldBlock => return error.Unexpected,420 error.WouldBlock => return error.Unexpected,
421 error.BrokenPipe => return error.Unexpected,
422 error.ConnectionResetByPeer => return error.Unexpected,421 error.ConnectionResetByPeer => return error.Unexpected,
423 error.NotOpenForReading => return error.Unexpected,422 error.NotOpenForReading => return error.Unexpected,
424 error.SocketUnconnected => return error.Unexpected,423 error.SocketUnconnected => return error.Unexpected,