From 551e009da7be279293fe261b729280d29fcf81b0 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Sat, 16 Aug 2025 11:32:10 +0200 Subject: [PATCH] Build.Step.Run: fix missing stdin buffer and flush Writer.sendFileAll() asserts non-zero buffer capacity in the case that the fallback is hit. It also requires the caller to flush. The buffer may be bypassed as an optimization but this is not a guarantee. Also improve the Writer documentation and add an earlier assert on buffer capacity in sendFileAll(). --- lib/std/Build/Step/Run.zig | 12 +++++++++--- lib/std/Io/Writer.zig | 12 ++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 823e9a8b41..78cf08dd43 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -1780,9 +1780,10 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !StdIoResult { }; defer file.close(); // TODO https://github.com/ziglang/zig/issues/23955 - var buffer: [1024]u8 = undefined; - var file_reader = file.reader(&buffer); - var stdin_writer = child.stdin.?.writer(&.{}); + var read_buffer: [1024]u8 = undefined; + var file_reader = file.reader(&read_buffer); + var write_buffer: [1024]u8 = undefined; + var stdin_writer = child.stdin.?.writer(&write_buffer); _ = stdin_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) { error.ReadFailed => return run.step.fail("failed to read from {f}: {t}", .{ path, file_reader.err.?, @@ -1791,6 +1792,11 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !StdIoResult { stdin_writer.err.?, }), }; + stdin_writer.interface.flush() catch |err| switch (err) { + error.WriteFailed => return run.step.fail("failed to write to stdin: {t}", .{ + stdin_writer.err.?, + }), + }; child.stdin.?.close(); child.stdin = null; }, diff --git a/lib/std/Io/Writer.zig b/lib/std/Io/Writer.zig index 131e7ea518..2e64f30a2b 100644 --- a/lib/std/Io/Writer.zig +++ b/lib/std/Io/Writer.zig @@ -882,6 +882,9 @@ pub fn writeSliceSwap(w: *Writer, Elem: type, slice: []const Elem) Error!void { /// Unlike `writeSplat` and `writeVec`, this function will call into `VTable` /// even if there is enough buffer capacity for the file contents. /// +/// The caller is responsible for flushing. Although the buffer may be bypassed +/// as an optimization, this is not a guarantee. +/// /// Although it would be possible to eliminate `error.Unimplemented` from the /// error set by reading directly into the buffer in such case, this is not /// done because it is more efficient to do it higher up the call stack so that @@ -924,7 +927,16 @@ pub fn sendFileReading(w: *Writer, file_reader: *File.Reader, limit: Limit) File /// Number of bytes logically written is returned. This excludes bytes from /// `buffer` because they have already been logically written. +/// +/// The caller is responsible for flushing. Although the buffer may be bypassed +/// as an optimization, this is not a guarantee. +/// +/// Asserts nonzero buffer capacity. pub fn sendFileAll(w: *Writer, file_reader: *File.Reader, limit: Limit) FileAllError!usize { + // The fallback sendFileReadingAll() path asserts non-zero buffer capacity. + // Explicitly assert it here as well to ensure the assert is hit even if + // the fallback path is not taken. + assert(w.buffer.len > 0); var remaining = @intFromEnum(limit); while (remaining > 0) { const n = sendFile(w, file_reader, .limited(remaining)) catch |err| switch (err) {