Skip to content

Commit 96cbdd1

Browse files
committed
std.fs.File.Reader: fix sendFile logic
it wasn't accounting for both writer and reader buffering
1 parent b35c55e commit 96cbdd1

File tree

3 files changed

+128
-299
lines changed

3 files changed

+128
-299
lines changed

lib/std/c.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10497,9 +10497,9 @@ pub const sysconf = switch (native_os) {
1049710497

1049810498
pub const sf_hdtr = switch (native_os) {
1049910499
.freebsd, .macos, .ios, .tvos, .watchos, .visionos => extern struct {
10500-
headers: [*]const iovec_const,
10500+
headers: ?[*]const iovec_const,
1050110501
hdr_cnt: c_int,
10502-
trailers: [*]const iovec_const,
10502+
trailers: ?[*]const iovec_const,
1050310503
trl_cnt: c_int,
1050410504
},
1050510505
else => void,

lib/std/fs/File.zig

Lines changed: 126 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,7 +1435,7 @@ pub const Reader = struct {
14351435
}
14361436
return 0;
14371437
};
1438-
const n = @min(size - pos, std.math.maxInt(i64), @intFromEnum(limit));
1438+
const n = @min(size - pos, maxInt(i64), @intFromEnum(limit));
14391439
file.seekBy(n) catch |err| {
14401440
r.seek_err = err;
14411441
return 0;
@@ -1726,18 +1726,123 @@ pub const Writer = struct {
17261726
file_reader: *Reader,
17271727
limit: std.io.Limit,
17281728
) std.io.Writer.FileError!usize {
1729+
const reader_buffered = file_reader.interface.buffered();
1730+
if (reader_buffered.len >= @intFromEnum(limit))
1731+
return sendFileBuffered(io_w, file_reader, reader_buffered);
1732+
const writer_buffered = io_w.buffered();
1733+
const file_limit = @intFromEnum(limit) - reader_buffered.len;
17291734
const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
17301735
const out_fd = w.file.handle;
17311736
const in_fd = file_reader.file.handle;
1732-
// TODO try using copy_file_range on FreeBSD
1733-
// TODO try using sendfile on macOS
1734-
// TODO try using sendfile on FreeBSD
1737+
1738+
if (native_os == .freebsd and w.mode == .streaming) sf: {
1739+
// Try using sendfile on FreeBSD.
1740+
if (w.sendfile_err != null) break :sf;
1741+
const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf;
1742+
var hdtr_data: std.c.sf_hdtr = undefined;
1743+
var headers: [2]posix.iovec_const = undefined;
1744+
var headers_i: u8 = 0;
1745+
if (writer_buffered.len != 0) {
1746+
headers[headers_i] = .{ .base = writer_buffered.ptr, .len = writer_buffered.len };
1747+
headers_i += 1;
1748+
}
1749+
if (reader_buffered.len != 0) {
1750+
headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len };
1751+
headers_i += 1;
1752+
}
1753+
const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: {
1754+
hdtr_data = .{
1755+
.headers = &headers,
1756+
.hdr_cnt = headers_i,
1757+
.trailers = null,
1758+
.trl_cnt = 0,
1759+
};
1760+
break :b &hdtr_data;
1761+
};
1762+
var sbytes: std.c.off_t = undefined;
1763+
const nbytes: usize = @min(file_limit, maxInt(usize));
1764+
const flags = 0;
1765+
switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, nbytes, hdtr, &sbytes, flags))) {
1766+
.SUCCESS, .INTR => {},
1767+
.INVAL, .OPNOTSUPP, .NOTSOCK, .NOSYS => w.sendfile_err = error.UnsupportedOperation,
1768+
.BADF => if (builtin.mode == .Debug) @panic("race condition") else {
1769+
w.sendfile_err = error.Unexpected;
1770+
},
1771+
.FAULT => if (builtin.mode == .Debug) @panic("segmentation fault") else {
1772+
w.sendfile_err = error.Unexpected;
1773+
},
1774+
.NOTCONN => w.sendfile_err = error.BrokenPipe,
1775+
.AGAIN, .BUSY => if (sbytes == 0) {
1776+
w.sendfile_err = error.WouldBlock;
1777+
},
1778+
.IO => w.sendfile_err = error.InputOutput,
1779+
.PIPE => w.sendfile_err = error.BrokenPipe,
1780+
.NOBUFS => w.sendfile_err = error.SystemResources,
1781+
else => |err| w.sendfile_err = posix.unexpectedErrno(err),
1782+
}
1783+
const consumed = io_w.consume(@bitCast(sbytes));
1784+
file_reader.seekTo(file_reader.pos + consumed) catch return error.ReadFailed;
1785+
return consumed;
1786+
}
1787+
1788+
if (native_os.isDarwin() and w.mode == .streaming) sf: {
1789+
// Try using sendfile on macOS.
1790+
if (w.sendfile_err != null) break :sf;
1791+
const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf;
1792+
var hdtr_data: std.c.sf_hdtr = undefined;
1793+
var headers: [2]posix.iovec_const = undefined;
1794+
var headers_i: u8 = 0;
1795+
if (writer_buffered.len != 0) {
1796+
headers[headers_i] = .{ .base = writer_buffered.ptr, .len = writer_buffered.len };
1797+
headers_i += 1;
1798+
}
1799+
if (reader_buffered.len != 0) {
1800+
headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len };
1801+
headers_i += 1;
1802+
}
1803+
const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: {
1804+
hdtr_data = .{
1805+
.headers = &headers,
1806+
.hdr_cnt = headers_i,
1807+
.trailers = null,
1808+
.trl_cnt = 0,
1809+
};
1810+
break :b &hdtr_data;
1811+
};
1812+
const max_count = maxInt(i32); // Avoid EINVAL.
1813+
var sbytes: std.c.off_t = @min(file_limit, max_count);
1814+
const flags = 0;
1815+
switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, &sbytes, hdtr, flags))) {
1816+
.SUCCESS, .INTR => {},
1817+
.OPNOTSUPP, .NOTSOCK, .NOSYS => w.sendfile_err = error.UnsupportedOperation,
1818+
.BADF => if (builtin.mode == .Debug) @panic("race condition") else {
1819+
w.sendfile_err = error.Unexpected;
1820+
},
1821+
.FAULT => if (builtin.mode == .Debug) @panic("segmentation fault") else {
1822+
w.sendfile_err = error.Unexpected;
1823+
},
1824+
.INVAL => if (builtin.mode == .Debug) @panic("invalid API usage") else {
1825+
w.sendfile_err = error.Unexpected;
1826+
},
1827+
.NOTCONN => w.sendfile_err = error.BrokenPipe,
1828+
.AGAIN => if (sbytes == 0) {
1829+
w.sendfile_err = error.WouldBlock;
1830+
},
1831+
.IO => w.sendfile_err = error.InputOutput,
1832+
.PIPE => w.sendfile_err = error.BrokenPipe,
1833+
else => |err| w.sendfile_err = posix.unexpectedErrno(err),
1834+
}
1835+
const consumed = io_w.consume(@bitCast(sbytes));
1836+
file_reader.seekTo(file_reader.pos + consumed) catch return error.ReadFailed;
1837+
return consumed;
1838+
}
1839+
17351840
if (native_os == .linux and w.mode == .streaming) sf: {
17361841
// Try using sendfile on Linux.
17371842
if (w.sendfile_err != null) break :sf;
17381843
// Linux sendfile does not support headers.
1739-
const buffered = limit.slice(file_reader.interface.buffer);
1740-
if (io_w.end != 0 or buffered.len != 0) return drain(io_w, &.{buffered}, 1);
1844+
if (writer_buffered.len != 0 or reader_buffered.len != 0)
1845+
return sendFileBuffered(io_w, file_reader, reader_buffered);
17411846
const max_count = 0x7ffff000; // Avoid EINVAL.
17421847
var off: std.os.linux.off_t = undefined;
17431848
const off_ptr: ?*std.os.linux.off_t, const count: usize = switch (file_reader.mode) {
@@ -1784,15 +1889,16 @@ pub const Writer = struct {
17841889
w.pos += n;
17851890
return n;
17861891
}
1892+
17871893
const copy_file_range = switch (native_os) {
17881894
.freebsd => std.os.freebsd.copy_file_range,
17891895
.linux => if (std.c.versionCheck(.{ .major = 2, .minor = 27, .patch = 0 })) std.os.linux.wrapped.copy_file_range else {},
17901896
else => {},
17911897
};
17921898
if (@TypeOf(copy_file_range) != void) cfr: {
17931899
if (w.copy_file_range_err != null) break :cfr;
1794-
const buffered = limit.slice(file_reader.interface.buffer);
1795-
if (io_w.end != 0 or buffered.len != 0) return drain(io_w, &.{buffered}, 1);
1900+
if (writer_buffered.len != 0 or reader_buffered.len != 0)
1901+
return sendFileBuffered(io_w, file_reader, reader_buffered);
17961902
var off_in: i64 = undefined;
17971903
var off_out: i64 = undefined;
17981904
const off_in_ptr: ?*i64 = switch (file_reader.mode) {
@@ -1832,6 +1938,8 @@ pub const Writer = struct {
18321938
if (w.pos != 0) break :fcf;
18331939
if (limit != .unlimited) break :fcf;
18341940
const size = file_reader.getSize() catch break :fcf;
1941+
if (writer_buffered.len != 0 or reader_buffered.len != 0)
1942+
return sendFileBuffered(io_w, file_reader, reader_buffered);
18351943
const rc = std.c.fcopyfile(in_fd, out_fd, null, .{ .DATA = true });
18361944
switch (posix.errno(rc)) {
18371945
.SUCCESS => {},
@@ -1860,6 +1968,16 @@ pub const Writer = struct {
18601968
return error.Unimplemented;
18611969
}
18621970

1971+
fn sendFileBuffered(
1972+
io_w: *std.io.Writer,
1973+
file_reader: *Reader,
1974+
reader_buffered: []const u8,
1975+
) std.io.Writer.FileError!usize {
1976+
const n = try drain(io_w, &.{reader_buffered}, 1);
1977+
file_reader.seekTo(file_reader.pos + n) catch return error.ReadFailed;
1978+
return n;
1979+
}
1980+
18631981
pub fn seekTo(w: *Writer, offset: u64) SeekError!void {
18641982
switch (w.mode) {
18651983
.positional, .positional_reading => {

0 commit comments

Comments
 (0)