Commit b35c55e237

Andrew Kelley <andrew@ziglang.org>
2025-07-22 03:13:45
std.fs.File.Reader: fix seek position logic
1 parent fe10c66
Changed files (2)
lib
lib/std/fs/File.zig
@@ -1135,7 +1135,7 @@ pub const Reader = struct {
     err: ?ReadError = null,
     mode: Reader.Mode = .positional,
     /// Tracks the true seek position in the file. To obtain the logical
-    /// position, subtract the buffer size from this value.
+    /// position, use `logicalPos`.
     pos: u64 = 0,
     size: ?u64 = null,
     size_err: ?GetEndPosError = null,
@@ -1274,14 +1274,20 @@ pub const Reader = struct {
         }
     }
 
+    pub fn logicalPos(r: *const Reader) u64 {
+        return r.pos - r.interface.bufferedLen();
+    }
+
     fn setPosAdjustingBuffer(r: *Reader, offset: u64) void {
-        if (offset < r.pos or offset >= r.pos + r.interface.bufferedLen()) {
+        const logical_pos = logicalPos(r);
+        if (offset < logical_pos or offset >= r.pos) {
             r.interface.seek = 0;
             r.interface.end = 0;
+            r.pos = offset;
         } else {
-            r.interface.seek += @intCast(offset - r.pos);
+            const logical_delta: usize = @intCast(offset - logical_pos);
+            r.interface.seek += logical_delta;
         }
-        r.pos = offset;
     }
 
     /// Number of slices to store on the stack, when trying to send as many byte
lib/std/fs/test.zig
@@ -2060,7 +2060,7 @@ test "invalid UTF-8/WTF-8 paths" {
 }
 
 test "read file non vectored" {
-    var tmp_dir = std.testing.tmpDir(.{});
+    var tmp_dir = testing.tmpDir(.{});
     defer tmp_dir.cleanup();
 
     const contents = "hello, world!\n";
@@ -2085,6 +2085,47 @@ test "read file non vectored" {
             else => |e| return e,
         };
     }
-    try std.testing.expectEqualStrings(contents, w.buffered());
-    try std.testing.expectEqual(contents.len, i);
+    try testing.expectEqualStrings(contents, w.buffered());
+    try testing.expectEqual(contents.len, i);
+}
+
+test "seek keeping partial buffer" {
+    var tmp_dir = testing.tmpDir(.{});
+    defer tmp_dir.cleanup();
+
+    const contents = "0123456789";
+
+    const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true });
+    defer file.close();
+    {
+        var file_writer: std.fs.File.Writer = .init(file, &.{});
+        try file_writer.interface.writeAll(contents);
+        try file_writer.interface.flush();
+    }
+
+    var read_buffer: [3]u8 = undefined;
+    var file_reader: std.fs.File.Reader = .init(file, &read_buffer);
+
+    try testing.expectEqual(0, file_reader.logicalPos());
+
+    var buf: [4]u8 = undefined;
+    try file_reader.interface.readSliceAll(&buf);
+
+    if (file_reader.interface.bufferedLen() != 3) {
+        // Pass the test if the OS doesn't give us vectored reads.
+        return;
+    }
+
+    try testing.expectEqual(4, file_reader.logicalPos());
+    try testing.expectEqual(7, file_reader.pos);
+    try file_reader.seekTo(6);
+    try testing.expectEqual(6, file_reader.logicalPos());
+    try testing.expectEqual(7, file_reader.pos);
+
+    try testing.expectEqualStrings("0123", &buf);
+
+    const n = try file_reader.interface.readSliceShort(&buf);
+    try testing.expectEqual(4, n);
+
+    try testing.expectEqualStrings("6789", &buf);
 }