authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-08 16:35:32-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-09 12:06:32-07:00
log9ea4d9aa3bc184a1be1199f3b519796d417c7765
tree27992d1a191d5f1dd7e0a59e1ee84f281ffe3202
parent5c0ac90721ec45c6712535a99cb90df48e59e121

std: fix sendFileReading not accounting for buffer

Related to 1d764c1fdf04829cec5974d82cec901825a80e49 Test case provided by: Co-authored-by: Kendall Condon <goon.pri.low@gmail.com>

2 files changed, 124 insertions(+), 99 deletions(-)

lib/std/Io/Writer.zig+31-6
......@@ -917,10 +917,12 @@ pub fn sendFileHeader(
917917 return n;
918918}
919919
920/// Asserts nonzero buffer capacity.
920/// Asserts nonzero buffer capacity and nonzero `limit`.
921921pub fn sendFileReading(w: *Writer, file_reader: *File.Reader, limit: Limit) FileReadingError!usize {
922 assert(limit != .nothing);
922923 const dest = limit.slice(try w.writableSliceGreedy(1));
923 const n = try file_reader.read(dest);
924 const n = try file_reader.interface.readSliceShort(dest);
925 if (n == 0) return error.EndOfStream;
924926 w.advance(n);
925927 return n;
926928}
......@@ -2655,7 +2657,8 @@ pub const Allocating = struct {
26552657 if (additional == 0) return error.EndOfStream;
26562658 list.ensureUnusedCapacity(gpa, limit.minInt64(additional)) catch return error.WriteFailed;
26572659 const dest = limit.slice(list.unusedCapacitySlice());
2658 const n = try file_reader.read(dest);
2660 const n = try file_reader.interface.readSliceShort(dest);
2661 if (n == 0) return error.EndOfStream;
26592662 list.items.len += n;
26602663 return n;
26612664 }
......@@ -2714,18 +2717,40 @@ test "allocating sendFile" {
27142717
27152718 const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true });
27162719 defer file.close();
2717 var r_buffer: [256]u8 = undefined;
2720 var r_buffer: [2]u8 = undefined;
27182721 var file_writer: std.fs.File.Writer = .init(file, &r_buffer);
2719 try file_writer.interface.writeByte('h');
2722 try file_writer.interface.writeAll("abcd");
27202723 try file_writer.interface.flush();
27212724
27222725 var file_reader = file_writer.moveToReader();
27232726 try file_reader.seekTo(0);
2727 try file_reader.interface.fill(2);
27242728
27252729 var allocating: Writer.Allocating = .init(testing.allocator);
27262730 defer allocating.deinit();
2731 try allocating.ensureUnusedCapacity(1);
2732 try testing.expectEqual(4, allocating.writer.sendFileAll(&file_reader, .unlimited));
2733 try testing.expectEqualStrings("abcd", allocating.writer.buffered());
2734}
2735
2736test sendFileReading {
2737 var tmp_dir = testing.tmpDir(.{});
2738 defer tmp_dir.cleanup();
2739
2740 const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true });
2741 defer file.close();
2742 var r_buffer: [2]u8 = undefined;
2743 var file_writer: std.fs.File.Writer = .init(file, &r_buffer);
2744 try file_writer.interface.writeAll("abcd");
2745 try file_writer.interface.flush();
27272746
2728 _ = try file_reader.interface.streamRemaining(&allocating.writer);
2747 var file_reader = file_writer.moveToReader();
2748 try file_reader.seekTo(0);
2749 try file_reader.interface.fill(2);
2750
2751 var w_buffer: [1]u8 = undefined;
2752 var discarding: Writer.Discarding = .init(&w_buffer);
2753 try testing.expectEqual(4, discarding.writer.sendFileReadingAll(&file_reader, .unlimited));
27292754}
27302755
27312756test writeStruct {
lib/std/fs/File.zig+93-93
......@@ -1322,13 +1322,15 @@ pub const Reader = struct {
13221322 },
13231323 .positional_reading => {
13241324 const dest = limit.slice(try w.writableSliceGreedy(1));
1325 const n = try readPositional(r, dest);
1325 var data: [1][]u8 = .{dest};
1326 const n = try readVecPositional(r, &data);
13261327 w.advance(n);
13271328 return n;
13281329 },
13291330 .streaming_reading => {
13301331 const dest = limit.slice(try w.writableSliceGreedy(1));
1331 const n = try readStreaming(r, dest);
1332 var data: [1][]u8 = .{dest};
1333 const n = try readVecStreaming(r, &data);
13321334 w.advance(n);
13331335 return n;
13341336 },
......@@ -1339,92 +1341,98 @@ pub const Reader = struct {
13391341 fn readVec(io_reader: *std.Io.Reader, data: [][]u8) std.Io.Reader.Error!usize {
13401342 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
13411343 switch (r.mode) {
1342 .positional, .positional_reading => {
1343 if (is_windows) {
1344 // Unfortunately, `ReadFileScatter` cannot be used since it
1345 // requires page alignment.
1346 if (io_reader.seek == io_reader.end) {
1347 io_reader.seek = 0;
1348 io_reader.end = 0;
1349 }
1350 const first = data[0];
1351 if (first.len >= io_reader.buffer.len - io_reader.end) {
1352 return readPositional(r, first);
1353 } else {
1354 io_reader.end += try readPositional(r, io_reader.buffer[io_reader.end..]);
1355 return 0;
1356 }
1357 }
1358 var iovecs_buffer: [max_buffers_len]posix.iovec = undefined;
1359 const dest_n, const data_size = try io_reader.writableVectorPosix(&iovecs_buffer, data);
1360 const dest = iovecs_buffer[0..dest_n];
1361 assert(dest[0].len > 0);
1362 const n = posix.preadv(r.file.handle, dest, r.pos) catch |err| switch (err) {
1363 error.Unseekable => {
1364 r.mode = r.mode.toStreaming();
1365 const pos = r.pos;
1366 if (pos != 0) {
1367 r.pos = 0;
1368 r.seekBy(@intCast(pos)) catch {
1369 r.mode = .failure;
1370 return error.ReadFailed;
1371 };
1372 }
1373 return 0;
1374 },
1375 else => |e| {
1376 r.err = e;
1344 .positional, .positional_reading => return readVecPositional(r, data),
1345 .streaming, .streaming_reading => return readVecStreaming(r, data),
1346 .failure => return error.ReadFailed,
1347 }
1348 }
1349
1350 fn readVecPositional(r: *Reader, data: [][]u8) std.Io.Reader.Error!usize {
1351 const io_reader = &r.interface;
1352 if (is_windows) {
1353 // Unfortunately, `ReadFileScatter` cannot be used since it
1354 // requires page alignment.
1355 if (io_reader.seek == io_reader.end) {
1356 io_reader.seek = 0;
1357 io_reader.end = 0;
1358 }
1359 const first = data[0];
1360 if (first.len >= io_reader.buffer.len - io_reader.end) {
1361 return readPositional(r, first);
1362 } else {
1363 io_reader.end += try readPositional(r, io_reader.buffer[io_reader.end..]);
1364 return 0;
1365 }
1366 }
1367 var iovecs_buffer: [max_buffers_len]posix.iovec = undefined;
1368 const dest_n, const data_size = try io_reader.writableVectorPosix(&iovecs_buffer, data);
1369 const dest = iovecs_buffer[0..dest_n];
1370 assert(dest[0].len > 0);
1371 const n = posix.preadv(r.file.handle, dest, r.pos) catch |err| switch (err) {
1372 error.Unseekable => {
1373 r.mode = r.mode.toStreaming();
1374 const pos = r.pos;
1375 if (pos != 0) {
1376 r.pos = 0;
1377 r.seekBy(@intCast(pos)) catch {
1378 r.mode = .failure;
13771379 return error.ReadFailed;
1378 },
1379 };
1380 if (n == 0) {
1381 r.size = r.pos;
1382 return error.EndOfStream;
1383 }
1384 r.pos += n;
1385 if (n > data_size) {
1386 io_reader.end += n - data_size;
1387 return data_size;
1380 };
13881381 }
1389 return n;
1382 return 0;
13901383 },
1391 .streaming, .streaming_reading => {
1392 if (is_windows) {
1393 // Unfortunately, `ReadFileScatter` cannot be used since it
1394 // requires page alignment.
1395 if (io_reader.seek == io_reader.end) {
1396 io_reader.seek = 0;
1397 io_reader.end = 0;
1398 }
1399 const first = data[0];
1400 if (first.len >= io_reader.buffer.len - io_reader.end) {
1401 return readStreaming(r, first);
1402 } else {
1403 io_reader.end += try readStreaming(r, io_reader.buffer[io_reader.end..]);
1404 return 0;
1405 }
1406 }
1407 var iovecs_buffer: [max_buffers_len]posix.iovec = undefined;
1408 const dest_n, const data_size = try io_reader.writableVectorPosix(&iovecs_buffer, data);
1409 const dest = iovecs_buffer[0..dest_n];
1410 assert(dest[0].len > 0);
1411 const n = posix.readv(r.file.handle, dest) catch |err| {
1412 r.err = err;
1413 return error.ReadFailed;
1414 };
1415 if (n == 0) {
1416 r.size = r.pos;
1417 return error.EndOfStream;
1418 }
1419 r.pos += n;
1420 if (n > data_size) {
1421 io_reader.end += n - data_size;
1422 return data_size;
1423 }
1424 return n;
1384 else => |e| {
1385 r.err = e;
1386 return error.ReadFailed;
14251387 },
1426 .failure => return error.ReadFailed,
1388 };
1389 if (n == 0) {
1390 r.size = r.pos;
1391 return error.EndOfStream;
1392 }
1393 r.pos += n;
1394 if (n > data_size) {
1395 io_reader.end += n - data_size;
1396 return data_size;
14271397 }
1398 return n;
1399 }
1400
1401 fn readVecStreaming(r: *Reader, data: [][]u8) std.Io.Reader.Error!usize {
1402 const io_reader = &r.interface;
1403 if (is_windows) {
1404 // Unfortunately, `ReadFileScatter` cannot be used since it
1405 // requires page alignment.
1406 if (io_reader.seek == io_reader.end) {
1407 io_reader.seek = 0;
1408 io_reader.end = 0;
1409 }
1410 const first = data[0];
1411 if (first.len >= io_reader.buffer.len - io_reader.end) {
1412 return readStreaming(r, first);
1413 } else {
1414 io_reader.end += try readStreaming(r, io_reader.buffer[io_reader.end..]);
1415 return 0;
1416 }
1417 }
1418 var iovecs_buffer: [max_buffers_len]posix.iovec = undefined;
1419 const dest_n, const data_size = try io_reader.writableVectorPosix(&iovecs_buffer, data);
1420 const dest = iovecs_buffer[0..dest_n];
1421 assert(dest[0].len > 0);
1422 const n = posix.readv(r.file.handle, dest) catch |err| {
1423 r.err = err;
1424 return error.ReadFailed;
1425 };
1426 if (n == 0) {
1427 r.size = r.pos;
1428 return error.EndOfStream;
1429 }
1430 r.pos += n;
1431 if (n > data_size) {
1432 io_reader.end += n - data_size;
1433 return data_size;
1434 }
1435 return n;
14281436 }
14291437
14301438 fn discard(io_reader: *std.Io.Reader, limit: std.Io.Limit) std.Io.Reader.Error!usize {
......@@ -1493,7 +1501,7 @@ pub const Reader = struct {
14931501 }
14941502 }
14951503
1496 pub fn readPositional(r: *Reader, dest: []u8) std.Io.Reader.Error!usize {
1504 fn readPositional(r: *Reader, dest: []u8) std.Io.Reader.Error!usize {
14971505 const n = r.file.pread(dest, r.pos) catch |err| switch (err) {
14981506 error.Unseekable => {
14991507 r.mode = r.mode.toStreaming();
......@@ -1520,7 +1528,7 @@ pub const Reader = struct {
15201528 return n;
15211529 }
15221530
1523 pub fn readStreaming(r: *Reader, dest: []u8) std.Io.Reader.Error!usize {
1531 fn readStreaming(r: *Reader, dest: []u8) std.Io.Reader.Error!usize {
15241532 const n = r.file.read(dest) catch |err| {
15251533 r.err = err;
15261534 return error.ReadFailed;
......@@ -1533,14 +1541,6 @@ pub const Reader = struct {
15331541 return n;
15341542 }
15351543
1536 pub fn read(r: *Reader, dest: []u8) std.Io.Reader.Error!usize {
1537 switch (r.mode) {
1538 .positional, .positional_reading => return readPositional(r, dest),
1539 .streaming, .streaming_reading => return readStreaming(r, dest),
1540 .failure => return error.ReadFailed,
1541 }
1542 }
1543
15441544 pub fn atEnd(r: *Reader) bool {
15451545 // Even if stat fails, size is set when end is encountered.
15461546 const size = r.size orelse return false;