Commit 5c0ac90721

Andrew Kelley <andrew@ziglang.org>
2025-10-09 00:28:36
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>
1 parent acd6ffd
Changed files (2)
lib
lib/std/fs/File.zig
@@ -1242,7 +1242,7 @@ pub const Reader = struct {
     pub fn seekBy(r: *Reader, offset: i64) Reader.SeekError!void {
         switch (r.mode) {
             .positional, .positional_reading => {
-                setPosAdjustingBuffer(r, @intCast(@as(i64, @intCast(r.pos)) + offset));
+                setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));
             },
             .streaming, .streaming_reading => {
                 if (posix.SEEK == void) {
@@ -1251,7 +1251,7 @@ pub const Reader = struct {
                 }
                 const seek_err = r.seek_err orelse e: {
                     if (posix.lseek_CUR(r.file.handle, offset)) |_| {
-                        setPosAdjustingBuffer(r, @intCast(@as(i64, @intCast(r.pos)) + offset));
+                        setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));
                         return;
                     } else |err| {
                         r.seek_err = err;
@@ -1275,16 +1275,16 @@ pub const Reader = struct {
     pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void {
         switch (r.mode) {
             .positional, .positional_reading => {
-                setPosAdjustingBuffer(r, offset);
+                setLogicalPos(r, offset);
             },
             .streaming, .streaming_reading => {
-                if (offset >= r.pos) return Reader.seekBy(r, @intCast(offset - r.pos));
+                if (offset >= r.pos) return Reader.seekBy(r, @intCast(offset - logicalPos(r)));
                 if (r.seek_err) |err| return err;
                 posix.lseek_SET(r.file.handle, offset) catch |err| {
                     r.seek_err = err;
                     return err;
                 };
-                setPosAdjustingBuffer(r, offset);
+                setLogicalPos(r, offset);
             },
             .failure => return r.seek_err.?,
         }
@@ -1294,7 +1294,7 @@ pub const Reader = struct {
         return r.pos - r.interface.bufferedLen();
     }
 
-    fn setPosAdjustingBuffer(r: *Reader, offset: u64) void {
+    fn setLogicalPos(r: *Reader, offset: u64) void {
         const logical_pos = logicalPos(r);
         if (offset < logical_pos or offset >= r.pos) {
             r.interface.seek = 0;
@@ -1855,7 +1855,7 @@ pub const Writer = struct {
                 return error.EndOfStream;
             }
             const consumed = io_w.consume(@intCast(sbytes));
-            file_reader.seekTo(file_reader.pos + consumed) catch return error.ReadFailed;
+            file_reader.seekBy(@intCast(consumed)) catch return error.ReadFailed;
             return consumed;
         }
 
@@ -1916,7 +1916,7 @@ pub const Writer = struct {
                 return error.EndOfStream;
             }
             const consumed = io_w.consume(@bitCast(len));
-            file_reader.seekTo(file_reader.pos + consumed) catch return error.ReadFailed;
+            file_reader.seekBy(@intCast(consumed)) catch return error.ReadFailed;
             return consumed;
         }
 
@@ -2049,7 +2049,7 @@ pub const Writer = struct {
         reader_buffered: []const u8,
     ) std.Io.Writer.FileError!usize {
         const n = try drain(io_w, &.{reader_buffered}, 1);
-        file_reader.seekTo(file_reader.pos + n) catch return error.ReadFailed;
+        file_reader.seekBy(@intCast(n)) catch return error.ReadFailed;
         return n;
     }
 
lib/std/fs/test.zig
@@ -2145,3 +2145,34 @@ test "seekBy" {
     try testing.expectEqual(15, n);
     try testing.expectEqualStrings("t's test seekBy", buffer[0..15]);
 }
+
+test "File.Writer sendfile with buffered contents" {
+    var tmp_dir = testing.tmpDir(.{});
+    defer tmp_dir.cleanup();
+
+    {
+        try tmp_dir.dir.writeFile(.{ .sub_path = "a", .data = "bcd" });
+        const in = try tmp_dir.dir.openFile("a", .{});
+        defer in.close();
+        const out = try tmp_dir.dir.createFile("b", .{});
+        defer out.close();
+
+        var in_buf: [2]u8 = undefined;
+        var in_r = in.reader(&in_buf);
+        _ = try in_r.getSize(); // Catch seeks past end by populating size
+        try in_r.interface.fill(2);
+
+        var out_buf: [1]u8 = undefined;
+        var out_w = out.writerStreaming(&out_buf);
+        try out_w.interface.writeByte('a');
+        try testing.expectEqual(3, try out_w.interface.sendFileAll(&in_r, .unlimited));
+        try out_w.interface.flush();
+    }
+
+    var check = try tmp_dir.dir.openFile("b", .{});
+    defer check.close();
+    var check_buf: [4]u8 = undefined;
+    var check_r = check.reader(&check_buf);
+    try testing.expectEqualStrings("abcd", try check_r.interface.take(4));
+    try testing.expectError(error.EndOfStream, check_r.interface.takeByte());
+}