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 {
264264 // always check if there's some data waiting to be read first.
265265 if (poll_fd.revents & posix.POLL.IN != 0) {
266266 const buf = try writableSliceGreedyAlloc(r, gpa, bump_amt);
267 const amt = posix.read(poll_fd.fd, buf) catch |err| switch (err) {
268 error.BrokenPipe => 0, // Handle the same as EOF.
269 else => |e| return e,
270 };
267 const amt = try posix.read(poll_fd.fd, buf);
271268 advanceBufferEnd(r, amt);
272269 if (amt == 0) {
273270 // Remove the fd when the EOF condition is met.
......@@ -633,9 +630,9 @@ pub const VTable = struct {
633630 fileWritePositional: *const fn (?*anyopaque, File, header: []const u8, data: []const []const u8, splat: usize, offset: u64) File.WritePositionalError!usize,
634631 fileWriteFileStreaming: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit) File.Writer.WriteFileError!usize,
635632 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.
637 fileReadStreaming: *const fn (?*anyopaque, File, data: []const []u8) File.Reader.Error!usize,
638 /// Returns 0 on end of stream.
633 /// May return 0 reads which is different than `error.EndOfStream`.
634 fileReadStreaming: *const fn (?*anyopaque, File, data: []const []u8) File.ReadStreamingError!usize,
635 /// Returns 0 if reading at or past the end.
639636 fileReadPositional: *const fn (?*anyopaque, File, data: []const []u8, offset: u64) File.ReadPositionalError!usize,
640637 fileSeekBy: *const fn (?*anyopaque, File, relative_offset: i64) File.SeekError!void,
641638 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 {
550550 });
551551}
552552
553pub const ReadStreamingError = error{EndOfStream} || Reader.Error;
554
553555/// Returns 0 on stream end or if `buffer` has no space available for data.
554556///
555557/// See also:
556558/// * `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 {
558560 return io.vtable.fileReadStreaming(io.userdata, file, buffer);
559561}
560562
......@@ -563,7 +565,6 @@ pub const ReadPositionalError = error{
563565 SystemResources,
564566 /// Trying to read a directory file descriptor as if it were a file.
565567 IsDir,
566 BrokenPipe,
567568 /// Non-blocking has been enabled, and reading from the file descriptor
568569 /// would block.
569570 WouldBlock,
lib/std/Io/File/Reader.zig+18-15
......@@ -31,7 +31,6 @@ pub const Error = error{
3131 SystemResources,
3232 /// Trying to read a directory file descriptor as if it were a file.
3333 IsDir,
34 BrokenPipe,
3534 ConnectionResetByPeer,
3635 /// File was not opened with read capability.
3736 NotOpenForReading,
......@@ -300,14 +299,16 @@ fn readVecStreaming(r: *Reader, data: [][]u8) Io.Reader.Error!usize {
300299 const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data);
301300 const dest = iovecs_buffer[0..dest_n];
302301 assert(dest[0].len > 0);
303 const n = io.vtable.fileReadStreaming(io.userdata, r.file, dest) catch |err| {
304 r.err = err;
305 return error.ReadFailed;
302 const n = io.vtable.fileReadStreaming(io.userdata, r.file, dest) catch |err| switch (err) {
303 error.EndOfStream => {
304 r.size = r.pos;
305 return error.EndOfStream;
306 },
307 else => |e| {
308 r.err = e;
309 return error.ReadFailed;
310 },
306311 };
307 if (n == 0) {
308 r.size = r.pos;
309 return error.EndOfStream;
310 }
311312 r.pos += n;
312313 if (n > data_size) {
313314 r.interface.end += n - data_size;
......@@ -355,14 +356,16 @@ fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
355356 const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, &data);
356357 const dest = iovecs_buffer[0..dest_n];
357358 assert(dest[0].len > 0);
358 const n = io.vtable.fileReadStreaming(io.userdata, file, dest) catch |err| {
359 r.err = err;
360 return error.ReadFailed;
359 const n = io.vtable.fileReadStreaming(io.userdata, file, dest) catch |err| switch (err) {
360 error.EndOfStream => {
361 r.size = r.pos;
362 return error.EndOfStream;
363 },
364 else => |e| {
365 r.err = e;
366 return error.ReadFailed;
367 },
361368 };
362 if (n == 0) {
363 r.size = r.pos;
364 return error.EndOfStream;
365 }
366369 r.pos += n;
367370 if (n > data_size) {
368371 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 {
81398139 for (files) |file| posix.close(file.handle);
81408140}
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 {
81438143 const t: *Threaded = @ptrCast(@alignCast(userdata));
81448144 _ = t;
81458145 if (is_windows) return fileReadStreamingWindows(file, data);
81468146 return fileReadStreamingPosix(file, data);
81478147}
81488148
8149fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usize {
8149fn fileReadStreamingPosix(file: File, data: []const []u8) File.ReadStreamingError!usize {
81508150 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
81518151 var i: usize = 0;
81528152 for (data) |buf| {
......@@ -8167,28 +8167,24 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usiz
81678167 switch (std.os.wasi.fd_read(file.handle, dest.ptr, dest.len, &nread)) {
81688168 .SUCCESS => {
81698169 syscall.finish();
8170 if (nread == 0) return error.EndOfStream;
81708171 return nread;
81718172 },
81728173 .INTR, .TIMEDOUT => {
81738174 try syscall.checkCancel();
81748175 continue;
81758176 },
8176 else => |e| {
8177 syscall.finish();
8178 switch (e) {
8179 .INVAL => |err| return errnoBug(err),
8180 .FAULT => |err| return errnoBug(err),
8181 .BADF => return error.IsDir, // File operation on directory.
8182 .IO => return error.InputOutput,
8183 .ISDIR => return error.IsDir,
8184 .NOBUFS => return error.SystemResources,
8185 .NOMEM => return error.SystemResources,
8186 .NOTCONN => return error.SocketUnconnected,
8187 .CONNRESET => return error.ConnectionResetByPeer,
8188 .NOTCAPABLE => return error.AccessDenied,
8189 else => |err| return posix.unexpectedErrno(err),
8190 }
8191 },
8177 .BADF => return syscall.fail(error.IsDir), // File operation on directory.
8178 .IO => return syscall.fail(error.InputOutput),
8179 .ISDIR => return syscall.fail(error.IsDir),
8180 .NOBUFS => return syscall.fail(error.SystemResources),
8181 .NOMEM => return syscall.fail(error.SystemResources),
8182 .NOTCONN => return syscall.fail(error.SocketUnconnected),
8183 .CONNRESET => return syscall.fail(error.ConnectionResetByPeer),
8184 .NOTCAPABLE => return syscall.fail(error.AccessDenied),
8185 .INVAL => |err| return syscall.errnoBug(err),
8186 .FAULT => |err| return syscall.errnoBug(err),
8187 else => |err| return syscall.unexpectedErrno(err),
81928188 }
81938189 }
81948190 }
......@@ -8199,36 +8195,33 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usiz
81998195 switch (posix.errno(rc)) {
82008196 .SUCCESS => {
82018197 syscall.finish();
8198 if (rc == 0) return error.EndOfStream;
82028199 return @intCast(rc);
82038200 },
82048201 .INTR, .TIMEDOUT => {
82058202 try syscall.checkCancel();
82068203 continue;
82078204 },
8208 else => |e| {
8205 .BADF => {
82098206 syscall.finish();
8210 switch (e) {
8211 .INVAL => |err| return errnoBug(err),
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 }
8207 if (native_os == .wasi) return error.IsDir; // File operation on directory.
8208 return error.NotOpenForReading;
82268209 },
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),
82278220 }
82288221 }
82298222}
82308223
8231fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!usize {
8224fn fileReadStreamingWindows(file: File, data: []const []u8) File.ReadStreamingError!usize {
82328225 var index: usize = 0;
82338226 while (index < data.len and data[index].len == 0) index += 1;
82348227 if (index == data.len) return 0;
......@@ -8250,25 +8243,18 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us
82508243 null, // key
82518244 )) {
82528245 .SUCCESS => {
8253 // Only END_OF_FILE is the true end.
8254 if (io_status_block.Information == 0) {
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 }
8246 syscall.finish();
8247 return io_status_block.Information;
82628248 },
82638249 .END_OF_FILE, .PIPE_BROKEN => {
82648250 syscall.finish();
8265 return io_status_block.Information;
8251 return error.EndOfStream;
82668252 },
8267 .PENDING => break,
82688253 .CANCELLED => {
82698254 try syscall.checkCancel();
82708255 continue;
82718256 },
8257 .PENDING => break,
82728258 .INVALID_DEVICE_REQUEST => return syscall.fail(error.IsDir),
82738259 .LOCK_NOT_GRANTED => return syscall.fail(error.LockViolation),
82748260 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
......@@ -8302,7 +8288,9 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us
83028288 alertable_syscall.finish();
83038289 }
83048290 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,
83068294 .PENDING => unreachable, // cannot return until the operation completes
83078295 .INVALID_DEVICE_REQUEST => return error.IsDir,
83088296 .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
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/zig/system.zig-1
......@@ -418,7 +418,6 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target {
418418 error.Canceled => |e| return e,
419419 error.Unexpected => |e| return e,
420420 error.WouldBlock => return error.Unexpected,
421 error.BrokenPipe => return error.Unexpected,
422421 error.ConnectionResetByPeer => return error.Unexpected,
423422 error.NotOpenForReading => return error.Unexpected,
424423 error.SocketUnconnected => return error.Unexpected,