authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-04-21 18:48:00-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:27-07:00
log4dc3a9444cd94d2d10b427d8dd7a3515f9dc96ea
treeec83f3652cf7b3c81ac1afed8d74084ad50c0052
parent19b82ca7ab7c44c3a1f4d0c6cbaf43bc0bded43a

std: reader/writer fixes


6 files changed, 251 insertions(+), 82 deletions(-)

lib/std/fs/File.zig+89-72
......@@ -931,7 +931,8 @@ pub const WriteFileError = PReadError || WriteError;
931931
932932pub fn writeFileAll(self: File, in_file: File, options: BufferedWriter.WriteFileOptions) WriteFileError!void {
933933 var file_writer = self.writer();
934 var bw = file_writer.interface().buffered(&.{});
934 var buffer: [2000]u8 = undefined;
935 var bw = file_writer.interface().buffered(&buffer);
935936 bw.writeFileAll(in_file, options) catch |err| switch (err) {
936937 error.WriteFailed => if (file_writer.err) |_| unreachable else |e| return e,
937938 else => |e| return e,
......@@ -940,14 +941,27 @@ pub fn writeFileAll(self: File, in_file: File, options: BufferedWriter.WriteFile
940941
941942pub const Reader = struct {
942943 file: File,
943 err: ReadError!void = {},
944 err: ?ReadError = null,
944945 mode: Reader.Mode = .positional,
945946 pos: u64 = 0,
946947 size: ?u64 = null,
947 size_err: GetEndPosError!void = {},
948 seek_err: SeekError!void = {},
949
950 pub const Mode = enum { streaming, positional };
948 size_err: ?GetEndPosError = null,
949 seek_err: ?SeekError = null,
950
951 pub const Mode = enum {
952 streaming,
953 positional,
954 streaming_reading,
955 positional_reading,
956
957 pub fn toStreaming(m: @This()) @This() {
958 return switch (m) {
959 .positional => .streaming,
960 .positional_reading => .streaming_reading,
961 else => unreachable,
962 };
963 }
964 };
951965
952966 pub fn interface(r: *Reader) std.io.Reader {
953967 return .{
......@@ -975,7 +989,7 @@ pub const Reader = struct {
975989 switch (r.mode) {
976990 .positional => {
977991 const size = r.size orelse {
978 if (r.file.getEndPos()) |size| {
992 if (file.getEndPos()) |size| {
979993 r.size = size;
980994 } else |err| {
981995 r.size_err = err;
......@@ -991,6 +1005,10 @@ pub const Reader = struct {
9911005 assert(pos == 0);
9921006 return 0;
9931007 },
1008 error.Unimplemented => {
1009 r.mode = .positional_reading;
1010 return 0;
1011 },
9941012 else => |e| {
9951013 r.err = e;
9961014 return error.ReadFailed;
......@@ -1003,12 +1021,45 @@ pub const Reader = struct {
10031021 const n = bw.writeFile(file, .none, limit, &.{}, 0) catch |err| switch (err) {
10041022 error.WriteFailed => return error.WriteFailed,
10051023 error.Unseekable => unreachable, // Passing `Offset.none`.
1024 error.Unimplemented => {
1025 r.mode = .streaming_reading;
1026 return 0;
1027 },
1028 else => |e| {
1029 r.err = e;
1030 return error.ReadFailed;
1031 },
1032 };
1033 r.pos = pos + n;
1034 return n;
1035 },
1036 .positional_reading => {
1037 const dest = limit.slice(try bw.writableSliceGreedy(1));
1038 const n = file.pread(dest, pos) catch |err| switch (err) {
1039 error.Unseekable => {
1040 r.mode = .streaming_reading;
1041 assert(pos == 0);
1042 return 0;
1043 },
10061044 else => |e| {
10071045 r.err = e;
10081046 return error.ReadFailed;
10091047 },
10101048 };
1049 if (n == 0) return error.EndOfStream;
1050 r.pos = pos + n;
1051 bw.advance(n);
1052 return n;
1053 },
1054 .streaming_reading => {
1055 const dest = limit.slice(try bw.writableSliceGreedy(1));
1056 const n = file.read(dest) catch |err| {
1057 r.err = err;
1058 return error.ReadFailed;
1059 };
1060 if (n == 0) return error.EndOfStream;
10111061 r.pos = pos + n;
1062 bw.advance(n);
10121063 return n;
10131064 },
10141065 }
......@@ -1020,7 +1071,7 @@ pub const Reader = struct {
10201071 const pos = r.pos;
10211072
10221073 switch (r.mode) {
1023 .positional => {
1074 .positional, .positional_reading => {
10241075 if (is_windows) {
10251076 // Unfortunately, `ReadFileScatter` cannot be used since it requires
10261077 // page alignment, so we are stuck using only the first slice.
......@@ -1053,7 +1104,7 @@ pub const Reader = struct {
10531104 if (send_vecs.len == 0) return 0; // Prevent false positive end detection on empty `data`.
10541105 const n = posix.preadv(handle, send_vecs, pos) catch |err| switch (err) {
10551106 error.Unseekable => {
1056 r.mode = .streaming;
1107 r.mode = r.mode.toStreaming();
10571108 assert(pos == 0);
10581109 return 0;
10591110 },
......@@ -1066,7 +1117,7 @@ pub const Reader = struct {
10661117 r.pos = pos + n;
10671118 return n;
10681119 },
1069 .streaming => {
1120 .streaming, .streaming_reading => {
10701121 if (is_windows) {
10711122 // Unfortunately, `ReadFileScatter` cannot be used since it requires
10721123 // page alignment, so we are stuck using only the first slice.
......@@ -1113,13 +1164,13 @@ pub const Reader = struct {
11131164 const file = r.file;
11141165 const pos = r.pos;
11151166 switch (r.mode) {
1116 .positional => {
1167 .positional, .positional_reading => {
11171168 const size = r.size orelse {
11181169 if (file.getEndPos()) |size| {
11191170 r.size = size;
11201171 } else |err| {
11211172 r.size_err = err;
1122 r.mode = .streaming;
1173 r.mode = r.mode.toStreaming();
11231174 }
11241175 return 0;
11251176 };
......@@ -1127,17 +1178,13 @@ pub const Reader = struct {
11271178 r.pos = pos + delta;
11281179 return delta;
11291180 },
1130 .streaming => {
1181 .streaming, .streaming_reading => {
11311182 // Unfortunately we can't seek forward without knowing the
11321183 // size because the seek syscalls provided to us will not
11331184 // return the true end position if a seek would exceed the
11341185 // end.
11351186 fallback: {
1136 if (r.size_err) |_| {
1137 if (r.seek_err) |_| {
1138 break :fallback;
1139 } else |_| {}
1140 } else |_| {}
1187 if (r.size_err == null and r.seek_err == null) break :fallback;
11411188 var trash_buffer: [std.atomic.cache_line]u8 = undefined;
11421189 const trash = &trash_buffer;
11431190 if (is_windows) {
......@@ -1188,9 +1235,16 @@ pub const Writer = struct {
11881235 err: WriteError!void = {},
11891236 mode: Writer.Mode = .positional,
11901237 pos: u64 = 0,
1238 sendfile_err: ?SendfileError = null,
1239 read_err: ?ReadError = null,
11911240
11921241 pub const Mode = Reader.Mode;
11931242
1243 pub const SendfileError = error{
1244 UnsupportedOperation,
1245 Unexpected,
1246 };
1247
11941248 /// Number of slices to store on the stack, when trying to send as many byte
11951249 /// vectors through the underlying write calls as possible.
11961250 const max_buffers_len = 16;
......@@ -1269,75 +1323,38 @@ pub const Writer = struct {
12691323 const w: *Writer = @ptrCast(@alignCast(context));
12701324 const out_fd = w.file.handle;
12711325 const in_fd = in_file.handle;
1272 const len_int = switch (in_limit) {
1273 .nothing => return writeSplat(context, headers_and_trailers, 1),
1274 .unlimited => 0,
1275 else => in_limit.toInt().?,
1276 };
1277 // TODO try using copy_file_range on linux
1278 // TODO try using copy_file_range on freebsd
1279 if (native_os == .linux) sf: {
1326 // TODO try using copy_file_range on Linux
1327 // TODO try using copy_file_range on FreeBSD
1328 // TODO try using sendfile on macOS
1329 // TODO try using sendfile on FreeBSD
1330 if (native_os == .linux and w.mode == .streaming) sf: {
1331 // Try using sendfile on Linux.
1332 if (w.sendfile_err != null) break :sf;
12801333 // Linux sendfile does not support headers or trailers but it does
12811334 // support a streaming read from in_file.
12821335 if (headers_len > 0) return writeSplat(context, headers_and_trailers[0..headers_len], 1);
12831336 const max_count = 0x7ffff000; // Avoid EINVAL.
1284 const smaller_len = if (len_int == 0) max_count else @min(len_int, max_count);
1337 const smaller_len = in_limit.minInt(max_count);
12851338 var off: std.os.linux.off_t = undefined;
12861339 const off_ptr: ?*std.os.linux.off_t = if (in_offset.toInt()) |offset| b: {
12871340 off = std.math.cast(std.os.linux.off_t, offset) orelse
12881341 return writeSplat(context, headers_and_trailers, 1);
12891342 break :b &off;
12901343 } else null;
1291 if (true) @panic("TODO");
12921344 const n = std.os.linux.wrapped.sendfile(out_fd, in_fd, off_ptr, smaller_len) catch |err| switch (err) {
1293 error.UnsupportedOperation => break :sf,
1294 error.Unseekable => break :sf,
1295 error.Unexpected => break :sf,
1345 // Errors that imply sendfile should be avoided on the next write.
1346 error.UnsupportedOperation,
1347 error.Unexpected,
1348 => |e| {
1349 w.sendfile_err = e;
1350 break :sf;
1351 },
12961352 else => |e| return e,
12971353 };
1298 if (in_offset.toInt()) |offset| {
1299 assert(n == off - offset);
1300 } else if (n == 0 and len_int == 0) {
1301 // The caller wouldn't be able to tell that the file transfer is
1302 // done and would incorrectly repeat the same call.
1303 return writeSplat(context, headers_and_trailers, 1);
1304 }
1354 w.pos += n;
13051355 return n;
13061356 }
1307 var iovecs_buffer: [max_buffers_len]std.posix.iovec_const = undefined;
1308 const iovecs = iovecs_buffer[0..@min(iovecs_buffer.len, headers_and_trailers.len)];
1309 for (iovecs, headers_and_trailers[0..iovecs.len]) |*v, d| v.* = .{ .base = d.ptr, .len = d.len };
1310 const headers = iovecs[0..@min(headers_len, iovecs.len)];
1311 const trailers = iovecs[headers.len..];
1312 const flags = 0;
1313 return posix.sendfile(out_fd, in_fd, in_offset, len_int, headers, trailers, flags) catch |err| switch (err) {
1314 error.Unseekable,
1315 error.FastOpenAlreadyInProgress,
1316 error.MessageTooBig,
1317 error.FileDescriptorNotASocket,
1318 error.NetworkUnreachable,
1319 error.NetworkSubsystemFailed,
1320 => return writeFileUnseekable(out_fd, in_fd, in_offset, in_limit, headers_and_trailers, headers_len),
1321
1322 else => |e| return e,
1323 };
1324 }
1325
1326 fn writeFileUnseekable(
1327 out_fd: Handle,
1328 in_fd: Handle,
1329 in_offset: u64,
1330 in_limit: std.io.Writer.Limit,
1331 headers_and_trailers: []const []const u8,
1332 headers_len: usize,
1333 ) std.io.Writer.FileError!usize {
1334 _ = out_fd;
1335 _ = in_fd;
1336 _ = in_offset;
1337 _ = in_limit;
1338 _ = headers_and_trailers;
1339 _ = headers_len;
1340 @panic("TODO writeFileUnseekable");
1357 return error.Unimplemented;
13411358 }
13421359};
13431360
lib/std/io/BufferedReader.zig+1-1
......@@ -99,7 +99,7 @@ pub fn readVecLimit(br: *BufferedReader, data: []const []u8, limit: Reader.Limit
9999
100100fn passthruRead(context: ?*anyopaque, bw: *BufferedWriter, limit: Reader.Limit) Reader.RwError!usize {
101101 const br: *BufferedReader = @alignCast(@ptrCast(context));
102 const buffer = limit.slice(br.buffer[br.end..br.seek]);
102 const buffer = limit.slice(br.buffer[br.seek..br.end]);
103103 if (buffer.len > 0) {
104104 const n = try bw.write(buffer);
105105 br.seek += n;
lib/std/io/BufferedWriter.zig+78-3
......@@ -480,6 +480,14 @@ pub fn writeSliceSwap(bw: *BufferedWriter, Elem: type, slice: []const Elem) Writ
480480/// Unlike `writeSplat` and `writeVec`, this function will call into the
481481/// underlying writer even if there is enough buffer capacity for the file
482482/// contents.
483///
484/// Although it would be possible to eliminate `error.Unimplemented` from
485/// the error set by reading directly into the buffer in such case,
486/// this is not done because it is more efficient to do it in `writeFileAll`
487/// so that the error does not occur with each write.
488///
489/// See `writeFileReading` for an alternative that does not have
490/// `error.Unimplemented` in the error set.
483491pub fn writeFile(
484492 bw: *BufferedWriter,
485493 file: std.fs.File,
......@@ -491,6 +499,23 @@ pub fn writeFile(
491499 return passthruWriteFile(bw, file, offset, limit, headers_and_trailers, headers_len);
492500}
493501
502pub const WriteFileReadingError = std.fs.File.PReadError || Writer.Error;
503
504/// Returning zero bytes means end of stream.
505///
506/// Asserts nonzero buffer capacity.
507pub fn writeFileReading(
508 bw: *BufferedWriter,
509 file: std.fs.File,
510 offset: Writer.Offset,
511 limit: Writer.Limit,
512) WriteFileReadingError!usize {
513 const dest = limit.slice(try bw.writableSliceGreedy(1));
514 const n = if (offset.toInt()) |pos| try file.pread(dest, pos) else try file.read(dest);
515 bw.advance(n);
516 return n;
517}
518
494519fn passthruWriteFile(
495520 context: ?*anyopaque,
496521 file: std.fs.File,
......@@ -588,7 +613,7 @@ pub const WriteFileOptions = struct {
588613 headers_len: usize = 0,
589614};
590615
591pub fn writeFileAll(bw: *BufferedWriter, file: std.fs.File, options: WriteFileOptions) Writer.FileError!void {
616pub fn writeFileAll(bw: *BufferedWriter, file: std.fs.File, options: WriteFileOptions) WriteFileReadingError!void {
592617 const headers_and_trailers = options.headers_and_trailers;
593618 const headers = headers_and_trailers[0..options.headers_len];
594619 switch (options.limit) {
......@@ -601,7 +626,15 @@ pub fn writeFileAll(bw: *BufferedWriter, file: std.fs.File, options: WriteFileOp
601626 var i: usize = 0;
602627 var offset = options.offset;
603628 while (true) {
604 var n = try bw.writeFile(file, offset, .unlimited, headers[i..], headers.len - i);
629 var n = bw.writeFile(file, offset, .unlimited, headers[i..], headers.len - i) catch |err| switch (err) {
630 error.Unimplemented => {
631 try bw.writeVecAll(headers[i..]);
632 try bw.writeFileReadingAll(file, offset, .unlimited);
633 try bw.writeVecAll(headers_and_trailers[headers.len..]);
634 return;
635 },
636 else => |e| return e,
637 };
605638 while (i < headers.len and n >= headers[i].len) {
606639 n -= headers[i].len;
607640 i += 1;
......@@ -619,7 +652,15 @@ pub fn writeFileAll(bw: *BufferedWriter, file: std.fs.File, options: WriteFileOp
619652 var i: usize = 0;
620653 var offset = options.offset;
621654 while (true) {
622 var n = try bw.writeFile(file, offset, .limited(len), headers_and_trailers[i..], headers.len - i);
655 var n = bw.writeFile(file, offset, .limited(len), headers_and_trailers[i..], headers.len - i) catch |err| switch (err) {
656 error.Unimplemented => {
657 try bw.writeVecAll(headers[i..]);
658 try bw.writeFileReadingAll(file, offset, .limited(len));
659 try bw.writeVecAll(headers_and_trailers[headers.len..]);
660 return;
661 },
662 else => |e| return e,
663 };
623664 while (i < headers.len and n >= headers[i].len) {
624665 n -= headers[i].len;
625666 i += 1;
......@@ -646,6 +687,40 @@ pub fn writeFileAll(bw: *BufferedWriter, file: std.fs.File, options: WriteFileOp
646687 }
647688}
648689
690/// Equivalent to `writeFileAll` but uses direct `pread` and `read` calls on
691/// `file` rather than `Writer.writeFile`. This is generally used as a fallback
692/// when the underlying implementation returns `error.Unimplemented`, which is
693/// why that error code does not appear in this function's error set.
694///
695/// Asserts nonzero buffer capacity.
696pub fn writeFileReadingAll(
697 bw: *BufferedWriter,
698 file: std.fs.File,
699 offset: Writer.Offset,
700 limit: Writer.Limit,
701) WriteFileReadingError!void {
702 if (offset.toInt()) |start_pos| {
703 var remaining = limit;
704 var pos = start_pos;
705 while (remaining.nonzero()) {
706 const dest = remaining.slice(try bw.writableSliceGreedy(1));
707 const n = try file.pread(dest, pos);
708 if (n == 0) return;
709 bw.advance(n);
710 pos += n;
711 remaining = remaining.subtract(n).?;
712 }
713 }
714 var remaining = limit;
715 while (remaining.nonzero()) {
716 const dest = remaining.slice(try bw.writableSliceGreedy(1));
717 const n = try file.read(dest);
718 if (n == 0) return;
719 bw.advance(n);
720 remaining = remaining.subtract(n).?;
721 }
722}
723
649724pub fn alignBuffer(
650725 bw: *BufferedWriter,
651726 buffer: []const u8,
lib/std/io/Writer.zig+18-4
......@@ -30,12 +30,20 @@ pub const VTable = struct {
3030 /// Number of bytes returned may be zero, which does not mean
3131 /// end-of-stream. A subsequent call may return nonzero, or may signal end
3232 /// of stream via `error.WriteFailed`.
33 ///
34 /// If `error.Unimplemented` is returned, the caller should do its own
35 /// reads from the file. The callee indicates it cannot offer a more
36 /// efficient implementation.
3337 writeFile: *const fn (
3438 ctx: ?*anyopaque,
3539 file: std.fs.File,
36 /// If this is `none`, `file` will be streamed, affecting the seek
37 /// position. Otherwise, it will be read positionally without affecting
38 /// the seek position.
40 /// If this is `Offset.none`, `file` will be streamed, affecting the
41 /// seek position. Otherwise, it will be read positionally without
42 /// affecting the seek position. `error.Unseekable` is only possible
43 /// when reading positionally.
44 ///
45 /// An offset past the end of the file is treated the same as an offset
46 /// equal to the end of the file.
3947 offset: Offset,
4048 /// Maximum amount of bytes to read from the file. Implementations may
4149 /// assume that the file size does not exceed this amount.
......@@ -52,7 +60,13 @@ pub const Error = error{
5260 WriteFailed,
5361};
5462
55pub const FileError = Error || std.fs.File.PReadError;
63pub const FileError = std.fs.File.PReadError || error{
64 /// See the `Writer` implementation for detailed diagnostics.
65 WriteFailed,
66 /// Indicates the caller should do its own file reading; the callee cannot
67 /// offer a more efficient implementation.
68 Unimplemented,
69};
5670
5771pub const Limit = std.io.Reader.Limit;
5872
lib/std/os/linux.zig+64-1
......@@ -9420,4 +9420,67 @@ pub const msghdr_const = extern struct {
94209420 control: ?*const anyopaque,
94219421 controllen: usize,
94229422 flags: u32,
9423};
\ No newline at end of file
9423};
9424
9425/// The syscalls, but with Zig error sets, going through libc if linking libc,
9426/// and with some footguns eliminated.
9427pub const wrapped = struct {
9428 pub const lfs64_abi = builtin.link_libc and (builtin.abi.isGnu() or builtin.abi.isAndroid());
9429 const system = if (builtin.link_libc) std.c else std.os.linux;
9430
9431 pub const SendfileError = std.posix.UnexpectedError || error{
9432 /// `out_fd` is an unconnected socket, or out_fd closed its read end.
9433 BrokenPipe,
9434 /// Descriptor is not valid or locked, or an mmap(2)-like operation is not available for in_fd.
9435 UnsupportedOperation,
9436 /// Nonblocking I/O has been selected but the write would block.
9437 WouldBlock,
9438 /// Unspecified error while reading from in_fd.
9439 InputOutput,
9440 /// Insufficient kernel memory to read from in_fd.
9441 SystemResources,
9442 /// `offset` is not `null` but the input file is not seekable.
9443 Unseekable,
9444 };
9445
9446 pub fn sendfile(
9447 out_fd: fd_t,
9448 in_fd: fd_t,
9449 in_offset: ?*off_t,
9450 in_len: usize,
9451 ) SendfileError!usize {
9452 const adjusted_len = @min(in_len, 0x7ffff000); // Prevents EOVERFLOW.
9453 const sendfileSymbol = if (lfs64_abi) system.sendfile64 else system.sendfile;
9454 const rc = sendfileSymbol(out_fd, in_fd, in_offset, adjusted_len);
9455 switch (errno(rc)) {
9456 .SUCCESS => return @bitCast(rc),
9457 .BADF => return invalidApiUsage(), // Always a race condition.
9458 .FAULT => return invalidApiUsage(), // Segmentation fault.
9459 .OVERFLOW => return unexpectedErrno(.OVERFLOW), // We avoid passing too large of a `count`.
9460 .NOTCONN => return error.BrokenPipe, // `out_fd` is an unconnected socket
9461 .INVAL => return error.UnsupportedOperation,
9462 .AGAIN => return error.WouldBlock,
9463 .IO => return error.InputOutput,
9464 .PIPE => return error.BrokenPipe,
9465 .NOMEM => return error.SystemResources,
9466 .NXIO => return error.Unseekable,
9467 .SPIPE => return error.Unseekable,
9468 else => |err| return unexpectedErrno(err),
9469 }
9470 }
9471
9472 const unexpectedErrno = std.posix.unexpectedErrno;
9473
9474 fn invalidApiUsage() error{Unexpected} {
9475 if (builtin.mode == .Debug) @panic("invalid API usage");
9476 return error.Unexpected;
9477 }
9478
9479 fn errno(rc: anytype) E {
9480 if (builtin.link_libc) {
9481 return if (rc == -1) @enumFromInt(std.c._errno().*) else .SUCCESS;
9482 } else {
9483 return errnoFromSyscall(rc);
9484 }
9485 }
9486};
lib/std/posix.zig+1-1
......@@ -7553,7 +7553,7 @@ pub fn ioctl_SIOCGIFINDEX(fd: fd_t, ifr: *ifreq) IoCtl_SIOCGIFINDEX_Error!void {
75537553 }
75547554}
75557555
7556const lfs64_abi = native_os == .linux and builtin.link_libc and (builtin.abi.isGnu() or builtin.abi.isAndroid());
7556const lfs64_abi = native_os == .linux and linux.wrapped.lfs64_abi;
75577557
75587558/// Whether or not `error.Unexpected` will print its value and a stack trace.
75597559///