authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-08 15:28:36-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-08 16:43:42-07:00
log4c27eeff8e25d59648680696a35235af2e067933
tree9d28e0d6d5273f0169d2b0a4109d790c3798346f
parent8964737ffc1502894f8261bb8a5ab541997f6f65

std: fix File.Writer sendfile with buffered contents

* File.Writer.seekBy passed wrong offset to setPosAdjustingBuffer. * File.Writer.sendFile incorrectly used non-logical position. Related to 1d764c1fdf04829cec5974d82cec901825a80e49 Test case provided by: Co-authored-by: Kendall Condon <goon.pri.low@gmail.com>

2 files changed, 40 insertions(+), 9 deletions(-)

lib/std/fs/File.zig+9-9
...@@ -1189,7 +1189,7 @@ pub const Reader = struct {...@@ -1189,7 +1189,7 @@ pub const Reader = struct {
1189 pub fn seekBy(r: *Reader, offset: i64) Reader.SeekError!void {1189 pub fn seekBy(r: *Reader, offset: i64) Reader.SeekError!void {
1190 switch (r.mode) {1190 switch (r.mode) {
1191 .positional, .positional_reading => {1191 .positional, .positional_reading => {
1192 setPosAdjustingBuffer(r, @intCast(@as(i64, @intCast(r.pos)) + offset));1192 setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));
1193 },1193 },
1194 .streaming, .streaming_reading => {1194 .streaming, .streaming_reading => {
1195 if (posix.SEEK == void) {1195 if (posix.SEEK == void) {
...@@ -1198,7 +1198,7 @@ pub const Reader = struct {...@@ -1198,7 +1198,7 @@ pub const Reader = struct {
1198 }1198 }
1199 const seek_err = r.seek_err orelse e: {1199 const seek_err = r.seek_err orelse e: {
1200 if (posix.lseek_CUR(r.file.handle, offset)) |_| {1200 if (posix.lseek_CUR(r.file.handle, offset)) |_| {
1201 setPosAdjustingBuffer(r, @intCast(@as(i64, @intCast(r.pos)) + offset));1201 setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));
1202 return;1202 return;
1203 } else |err| {1203 } else |err| {
1204 r.seek_err = err;1204 r.seek_err = err;
...@@ -1222,16 +1222,16 @@ pub const Reader = struct {...@@ -1222,16 +1222,16 @@ pub const Reader = struct {
1222 pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void {1222 pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void {
1223 switch (r.mode) {1223 switch (r.mode) {
1224 .positional, .positional_reading => {1224 .positional, .positional_reading => {
1225 setPosAdjustingBuffer(r, offset);1225 setLogicalPos(r, offset);
1226 },1226 },
1227 .streaming, .streaming_reading => {1227 .streaming, .streaming_reading => {
1228 if (offset >= r.pos) return Reader.seekBy(r, @intCast(offset - r.pos));1228 if (offset >= r.pos) return Reader.seekBy(r, @intCast(offset - logicalPos(r)));
1229 if (r.seek_err) |err| return err;1229 if (r.seek_err) |err| return err;
1230 posix.lseek_SET(r.file.handle, offset) catch |err| {1230 posix.lseek_SET(r.file.handle, offset) catch |err| {
1231 r.seek_err = err;1231 r.seek_err = err;
1232 return err;1232 return err;
1233 };1233 };
1234 setPosAdjustingBuffer(r, offset);1234 setLogicalPos(r, offset);
1235 },1235 },
1236 .failure => return r.seek_err.?,1236 .failure => return r.seek_err.?,
1237 }1237 }
...@@ -1241,7 +1241,7 @@ pub const Reader = struct {...@@ -1241,7 +1241,7 @@ pub const Reader = struct {
1241 return r.pos - r.interface.bufferedLen();1241 return r.pos - r.interface.bufferedLen();
1242 }1242 }
12431243
1244 fn setPosAdjustingBuffer(r: *Reader, offset: u64) void {1244 fn setLogicalPos(r: *Reader, offset: u64) void {
1245 const logical_pos = logicalPos(r);1245 const logical_pos = logicalPos(r);
1246 if (offset < logical_pos or offset >= r.pos) {1246 if (offset < logical_pos or offset >= r.pos) {
1247 r.interface.seek = 0;1247 r.interface.seek = 0;
...@@ -1804,7 +1804,7 @@ pub const Writer = struct {...@@ -1804,7 +1804,7 @@ pub const Writer = struct {
1804 return error.EndOfStream;1804 return error.EndOfStream;
1805 }1805 }
1806 const consumed = io_w.consume(@intCast(sbytes));1806 const consumed = io_w.consume(@intCast(sbytes));
1807 file_reader.seekTo(file_reader.pos + consumed) catch return error.ReadFailed;1807 file_reader.seekBy(@intCast(consumed)) catch return error.ReadFailed;
1808 return consumed;1808 return consumed;
1809 }1809 }
18101810
...@@ -1865,7 +1865,7 @@ pub const Writer = struct {...@@ -1865,7 +1865,7 @@ pub const Writer = struct {
1865 return error.EndOfStream;1865 return error.EndOfStream;
1866 }1866 }
1867 const consumed = io_w.consume(@bitCast(len));1867 const consumed = io_w.consume(@bitCast(len));
1868 file_reader.seekTo(file_reader.pos + consumed) catch return error.ReadFailed;1868 file_reader.seekBy(@intCast(consumed)) catch return error.ReadFailed;
1869 return consumed;1869 return consumed;
1870 }1870 }
18711871
...@@ -1998,7 +1998,7 @@ pub const Writer = struct {...@@ -1998,7 +1998,7 @@ pub const Writer = struct {
1998 reader_buffered: []const u8,1998 reader_buffered: []const u8,
1999 ) std.Io.Writer.FileError!usize {1999 ) std.Io.Writer.FileError!usize {
2000 const n = try drain(io_w, &.{reader_buffered}, 1);2000 const n = try drain(io_w, &.{reader_buffered}, 1);
2001 file_reader.seekTo(file_reader.pos + n) catch return error.ReadFailed;2001 file_reader.seekBy(@intCast(n)) catch return error.ReadFailed;
2002 return n;2002 return n;
2003 }2003 }
20042004
lib/std/fs/test.zig+31
...@@ -2250,3 +2250,34 @@ test "seekTo flushes buffered data" {...@@ -2250,3 +2250,34 @@ test "seekTo flushes buffered data" {
2250 try file_reader.interface.readSliceAll(&buf);2250 try file_reader.interface.readSliceAll(&buf);
2251 try std.testing.expectEqualStrings(contents, &buf);2251 try std.testing.expectEqualStrings(contents, &buf);
2252}2252}
2253
2254test "File.Writer sendfile with buffered contents" {
2255 var tmp_dir = testing.tmpDir(.{});
2256 defer tmp_dir.cleanup();
2257
2258 {
2259 try tmp_dir.dir.writeFile(.{ .sub_path = "a", .data = "bcd" });
2260 const in = try tmp_dir.dir.openFile("a", .{});
2261 defer in.close();
2262 const out = try tmp_dir.dir.createFile("b", .{});
2263 defer out.close();
2264
2265 var in_buf: [2]u8 = undefined;
2266 var in_r = in.reader(&in_buf);
2267 _ = try in_r.getSize(); // Catch seeks past end by populating size
2268 try in_r.interface.fill(2);
2269
2270 var out_buf: [1]u8 = undefined;
2271 var out_w = out.writerStreaming(&out_buf);
2272 try out_w.interface.writeByte('a');
2273 try testing.expectEqual(3, try out_w.interface.sendFileAll(&in_r, .unlimited));
2274 try out_w.interface.flush();
2275 }
2276
2277 var check = try tmp_dir.dir.openFile("b", .{});
2278 defer check.close();
2279 var check_buf: [4]u8 = undefined;
2280 var check_r = check.reader(&check_buf);
2281 try testing.expectEqualStrings("abcd", try check_r.interface.take(4));
2282 try testing.expectError(error.EndOfStream, check_r.interface.takeByte());
2283}