authorgravatar for goon.pri.low@gmail.comKendall Condon <goon.pri.low@gmail.com> 2025-09-01 16:42:45-04:00
committergravatar for goon.pri.low@gmail.comKendall Condon <goon.pri.low@gmail.com> 2025-09-04 17:26:49-04:00
log58dda3b10b8aacf377447d36dc97efc3a3f2e21a
treeebf0235cc301958e30c0dab1f15687c3b45d493d
parent7687b916fdb99605820bbbd515644707cc918703

fix sendFile implementations bypassing interface buffer

Also removes `File.Reader.read` since it is otherwise unused and is a footgun.

2 files changed, 43 insertions(+), 19 deletions(-)

lib/std/Io/Writer.zig+40-9
......@@ -921,7 +921,8 @@ pub fn sendFileHeader(
921921/// Asserts nonzero buffer capacity.
922922pub fn sendFileReading(w: *Writer, file_reader: *File.Reader, limit: Limit) FileReadingError!usize {
923923 const dest = limit.slice(try w.writableSliceGreedy(1));
924 const n = try file_reader.read(dest);
924 const n = try file_reader.interface.readSliceShort(dest);
925 if (n == 0) return error.EndOfStream;
925926 w.advance(n);
926927 return n;
927928}
......@@ -2276,6 +2277,12 @@ pub const Discarding = struct {
22762277 const d: *Discarding = @alignCast(@fieldParentPtr("writer", w));
22772278 d.count += w.end;
22782279 w.end = 0;
2280 const buffered_n = limit.minInt64(file_reader.interface.bufferedLen());
2281 if (buffered_n != 0) {
2282 file_reader.interface.toss(buffered_n);
2283 d.count += buffered_n;
2284 return buffered_n;
2285 }
22792286 if (limit == .nothing) return 0;
22802287 if (file_reader.getSize()) |size| {
22812288 const n = limit.minInt64(size - file_reader.pos);
......@@ -2767,7 +2774,9 @@ pub const Allocating = struct {
27672774 if (additional == 0) return error.EndOfStream;
27682775 a.ensureUnusedCapacity(limit.minInt64(additional)) catch return error.WriteFailed;
27692776 const dest = limit.slice(a.writer.buffer[a.writer.end..]);
2770 const n = try file_reader.read(dest);
2777 const n = try file_reader.interface.readSliceShort(dest);
2778 // If it was a short read, then EOF has been reached and `file_reader.size`
2779 // has been set and the EOF case will be hit on subsequent calls.
27712780 a.writer.end += n;
27722781 return n;
27732782 }
......@@ -2818,18 +2827,18 @@ test "discarding sendFile" {
28182827
28192828 const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true });
28202829 defer file.close();
2821 var r_buffer: [256]u8 = undefined;
2830 var r_buffer: [2]u8 = undefined;
28222831 var file_writer: std.fs.File.Writer = .init(file, &r_buffer);
2823 try file_writer.interface.writeByte('h');
2832 try file_writer.interface.writeAll("abcd");
28242833 try file_writer.interface.flush();
28252834
28262835 var file_reader = file_writer.moveToReader();
28272836 try file_reader.seekTo(0);
2837 try file_reader.interface.fill(2);
28282838
28292839 var w_buffer: [256]u8 = undefined;
28302840 var discarding: Writer.Discarding = .init(&w_buffer);
2831
2832 _ = try file_reader.interface.streamRemaining(&discarding.writer);
2841 try testing.expectEqual(4, discarding.writer.sendFileAll(&file_reader, .unlimited));
28332842}
28342843
28352844test "allocating sendFile" {
......@@ -2838,18 +2847,40 @@ test "allocating sendFile" {
28382847
28392848 const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true });
28402849 defer file.close();
2841 var r_buffer: [256]u8 = undefined;
2850 var r_buffer: [2]u8 = undefined;
28422851 var file_writer: std.fs.File.Writer = .init(file, &r_buffer);
2843 try file_writer.interface.writeByte('h');
2852 try file_writer.interface.writeAll("abcd");
28442853 try file_writer.interface.flush();
28452854
28462855 var file_reader = file_writer.moveToReader();
28472856 try file_reader.seekTo(0);
2857 try file_reader.interface.fill(2);
28482858
28492859 var allocating: Writer.Allocating = .init(testing.allocator);
28502860 defer allocating.deinit();
2861 try allocating.ensureUnusedCapacity(1);
2862 try testing.expectEqual(4, allocating.writer.sendFileAll(&file_reader, .unlimited));
2863 try testing.expectEqualStrings("abcd", allocating.writer.buffered());
2864}
2865
2866test sendFileReading {
2867 var tmp_dir = testing.tmpDir(.{});
2868 defer tmp_dir.cleanup();
28512869
2852 _ = try file_reader.interface.streamRemaining(&allocating.writer);
2870 const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true });
2871 defer file.close();
2872 var r_buffer: [2]u8 = undefined;
2873 var file_writer: std.fs.File.Writer = .init(file, &r_buffer);
2874 try file_writer.interface.writeAll("abcd");
2875 try file_writer.interface.flush();
2876
2877 var file_reader = file_writer.moveToReader();
2878 try file_reader.seekTo(0);
2879 try file_reader.interface.fill(2);
2880
2881 var w_buffer: [1]u8 = undefined;
2882 var discarding: Writer.Discarding = .init(&w_buffer);
2883 try testing.expectEqual(4, discarding.writer.sendFileReadingAll(&file_reader, .unlimited));
28532884}
28542885
28552886test writeStruct {
lib/std/fs/File.zig+3-10
......@@ -1154,6 +1154,7 @@ pub const Reader = struct {
11541154 };
11551155 }
11561156
1157 /// If `error.EndOfStream` has been hit, this cannot fail.
11571158 pub fn getSize(r: *Reader) SizeError!u64 {
11581159 return r.size orelse {
11591160 if (r.size_err) |err| return err;
......@@ -1440,7 +1441,7 @@ pub const Reader = struct {
14401441 }
14411442 }
14421443
1443 pub fn readPositional(r: *Reader, dest: []u8) std.Io.Reader.Error!usize {
1444 fn readPositional(r: *Reader, dest: []u8) std.Io.Reader.Error!usize {
14441445 const n = r.file.pread(dest, r.pos) catch |err| switch (err) {
14451446 error.Unseekable => {
14461447 r.mode = r.mode.toStreaming();
......@@ -1467,7 +1468,7 @@ pub const Reader = struct {
14671468 return n;
14681469 }
14691470
1470 pub fn readStreaming(r: *Reader, dest: []u8) std.Io.Reader.Error!usize {
1471 fn readStreaming(r: *Reader, dest: []u8) std.Io.Reader.Error!usize {
14711472 const n = r.file.read(dest) catch |err| {
14721473 r.err = err;
14731474 return error.ReadFailed;
......@@ -1480,14 +1481,6 @@ pub const Reader = struct {
14801481 return n;
14811482 }
14821483
1483 pub fn read(r: *Reader, dest: []u8) std.Io.Reader.Error!usize {
1484 switch (r.mode) {
1485 .positional, .positional_reading => return readPositional(r, dest),
1486 .streaming, .streaming_reading => return readStreaming(r, dest),
1487 .failure => return error.ReadFailed,
1488 }
1489 }
1490
14911484 pub fn atEnd(r: *Reader) bool {
14921485 // Even if stat fails, size is set when end is encountered.
14931486 const size = r.size orelse return false;