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(...@@ -917,10 +917,12 @@ pub fn sendFileHeader(
917 return n;917 return n;
918}918}
919919
920/// Asserts nonzero buffer capacity.920/// Asserts nonzero buffer capacity and nonzero `limit`.
921pub fn sendFileReading(w: *Writer, file_reader: *File.Reader, limit: Limit) FileReadingError!usize {921pub fn sendFileReading(w: *Writer, file_reader: *File.Reader, limit: Limit) FileReadingError!usize {
922 assert(limit != .nothing);
922 const dest = limit.slice(try w.writableSliceGreedy(1));923 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;
924 w.advance(n);926 w.advance(n);
925 return n;927 return n;
926}928}
...@@ -2655,7 +2657,8 @@ pub const Allocating = struct {...@@ -2655,7 +2657,8 @@ pub const Allocating = struct {
2655 if (additional == 0) return error.EndOfStream;2657 if (additional == 0) return error.EndOfStream;
2656 list.ensureUnusedCapacity(gpa, limit.minInt64(additional)) catch return error.WriteFailed;2658 list.ensureUnusedCapacity(gpa, limit.minInt64(additional)) catch return error.WriteFailed;
2657 const dest = limit.slice(list.unusedCapacitySlice());2659 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;
2659 list.items.len += n;2662 list.items.len += n;
2660 return n;2663 return n;
2661 }2664 }
...@@ -2714,18 +2717,40 @@ test "allocating sendFile" {...@@ -2714,18 +2717,40 @@ test "allocating sendFile" {
27142717
2715 const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true });2718 const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true });
2716 defer file.close();2719 defer file.close();
2717 var r_buffer: [256]u8 = undefined;2720 var r_buffer: [2]u8 = undefined;
2718 var file_writer: std.fs.File.Writer = .init(file, &r_buffer);2721 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");
2720 try file_writer.interface.flush();2723 try file_writer.interface.flush();
27212724
2722 var file_reader = file_writer.moveToReader();2725 var file_reader = file_writer.moveToReader();
2723 try file_reader.seekTo(0);2726 try file_reader.seekTo(0);
2727 try file_reader.interface.fill(2);
27242728
2725 var allocating: Writer.Allocating = .init(testing.allocator);2729 var allocating: Writer.Allocating = .init(testing.allocator);
2726 defer allocating.deinit();2730 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));
2729}2754}
27302755
2731test writeStruct {2756test writeStruct {
lib/std/fs/File.zig+93-93
...@@ -1322,13 +1322,15 @@ pub const Reader = struct {...@@ -1322,13 +1322,15 @@ pub const Reader = struct {
1322 },1322 },
1323 .positional_reading => {1323 .positional_reading => {
1324 const dest = limit.slice(try w.writableSliceGreedy(1));1324 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);
1326 w.advance(n);1327 w.advance(n);
1327 return n;1328 return n;
1328 },1329 },
1329 .streaming_reading => {1330 .streaming_reading => {
1330 const dest = limit.slice(try w.writableSliceGreedy(1));1331 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);
1332 w.advance(n);1334 w.advance(n);
1333 return n;1335 return n;
1334 },1336 },
...@@ -1339,92 +1341,98 @@ pub const Reader = struct {...@@ -1339,92 +1341,98 @@ pub const Reader = struct {
1339 fn readVec(io_reader: *std.Io.Reader, data: [][]u8) std.Io.Reader.Error!usize {1341 fn readVec(io_reader: *std.Io.Reader, data: [][]u8) std.Io.Reader.Error!usize {
1340 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));1342 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
1341 switch (r.mode) {1343 switch (r.mode) {
1342 .positional, .positional_reading => {1344 .positional, .positional_reading => return readVecPositional(r, data),
1343 if (is_windows) {1345 .streaming, .streaming_reading => return readVecStreaming(r, data),
1344 // Unfortunately, `ReadFileScatter` cannot be used since it1346 .failure => return error.ReadFailed,
1345 // requires page alignment.1347 }
1346 if (io_reader.seek == io_reader.end) {1348 }
1347 io_reader.seek = 0;1349
1348 io_reader.end = 0;1350 fn readVecPositional(r: *Reader, data: [][]u8) std.Io.Reader.Error!usize {
1349 }1351 const io_reader = &r.interface;
1350 const first = data[0];1352 if (is_windows) {
1351 if (first.len >= io_reader.buffer.len - io_reader.end) {1353 // Unfortunately, `ReadFileScatter` cannot be used since it
1352 return readPositional(r, first);1354 // requires page alignment.
1353 } else {1355 if (io_reader.seek == io_reader.end) {
1354 io_reader.end += try readPositional(r, io_reader.buffer[io_reader.end..]);1356 io_reader.seek = 0;
1355 return 0;1357 io_reader.end = 0;
1356 }1358 }
1357 }1359 const first = data[0];
1358 var iovecs_buffer: [max_buffers_len]posix.iovec = undefined;1360 if (first.len >= io_reader.buffer.len - io_reader.end) {
1359 const dest_n, const data_size = try io_reader.writableVectorPosix(&iovecs_buffer, data);1361 return readPositional(r, first);
1360 const dest = iovecs_buffer[0..dest_n];1362 } else {
1361 assert(dest[0].len > 0);1363 io_reader.end += try readPositional(r, io_reader.buffer[io_reader.end..]);
1362 const n = posix.preadv(r.file.handle, dest, r.pos) catch |err| switch (err) {1364 return 0;
1363 error.Unseekable => {1365 }
1364 r.mode = r.mode.toStreaming();1366 }
1365 const pos = r.pos;1367 var iovecs_buffer: [max_buffers_len]posix.iovec = undefined;
1366 if (pos != 0) {1368 const dest_n, const data_size = try io_reader.writableVectorPosix(&iovecs_buffer, data);
1367 r.pos = 0;1369 const dest = iovecs_buffer[0..dest_n];
1368 r.seekBy(@intCast(pos)) catch {1370 assert(dest[0].len > 0);
1369 r.mode = .failure;1371 const n = posix.preadv(r.file.handle, dest, r.pos) catch |err| switch (err) {
1370 return error.ReadFailed;1372 error.Unseekable => {
1371 };1373 r.mode = r.mode.toStreaming();
1372 }1374 const pos = r.pos;
1373 return 0;1375 if (pos != 0) {
1374 },1376 r.pos = 0;
1375 else => |e| {1377 r.seekBy(@intCast(pos)) catch {
1376 r.err = e;1378 r.mode = .failure;
1377 return error.ReadFailed;1379 return error.ReadFailed;
1378 },1380 };
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;
1388 }1381 }
1389 return n;1382 return 0;
1390 },1383 },
1391 .streaming, .streaming_reading => {1384 else => |e| {
1392 if (is_windows) {1385 r.err = e;
1393 // Unfortunately, `ReadFileScatter` cannot be used since it1386 return error.ReadFailed;
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;
1425 },1387 },
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;
1427 }1397 }
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;
1428 }1436 }
14291437
1430 fn discard(io_reader: *std.Io.Reader, limit: std.Io.Limit) std.Io.Reader.Error!usize {1438 fn discard(io_reader: *std.Io.Reader, limit: std.Io.Limit) std.Io.Reader.Error!usize {
...@@ -1493,7 +1501,7 @@ pub const Reader = struct {...@@ -1493,7 +1501,7 @@ pub const Reader = struct {
1493 }1501 }
1494 }1502 }
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 {
1497 const n = r.file.pread(dest, r.pos) catch |err| switch (err) {1505 const n = r.file.pread(dest, r.pos) catch |err| switch (err) {
1498 error.Unseekable => {1506 error.Unseekable => {
1499 r.mode = r.mode.toStreaming();1507 r.mode = r.mode.toStreaming();
...@@ -1520,7 +1528,7 @@ pub const Reader = struct {...@@ -1520,7 +1528,7 @@ pub const Reader = struct {
1520 return n;1528 return n;
1521 }1529 }
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 {
1524 const n = r.file.read(dest) catch |err| {1532 const n = r.file.read(dest) catch |err| {
1525 r.err = err;1533 r.err = err;
1526 return error.ReadFailed;1534 return error.ReadFailed;
...@@ -1533,14 +1541,6 @@ pub const Reader = struct {...@@ -1533,14 +1541,6 @@ pub const Reader = struct {
1533 return n;1541 return n;
1534 }1542 }
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
1544 pub fn atEnd(r: *Reader) bool {1544 pub fn atEnd(r: *Reader) bool {
1545 // Even if stat fails, size is set when end is encountered.1545 // Even if stat fails, size is set when end is encountered.
1546 const size = r.size orelse return false;1546 const size = r.size orelse return false;