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;...@@ -931,7 +931,8 @@ pub const WriteFileError = PReadError || WriteError;
931931
932pub fn writeFileAll(self: File, in_file: File, options: BufferedWriter.WriteFileOptions) WriteFileError!void {932pub fn writeFileAll(self: File, in_file: File, options: BufferedWriter.WriteFileOptions) WriteFileError!void {
933 var file_writer = self.writer();933 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);
935 bw.writeFileAll(in_file, options) catch |err| switch (err) {936 bw.writeFileAll(in_file, options) catch |err| switch (err) {
936 error.WriteFailed => if (file_writer.err) |_| unreachable else |e| return e,937 error.WriteFailed => if (file_writer.err) |_| unreachable else |e| return e,
937 else => |e| return e,938 else => |e| return e,
...@@ -940,14 +941,27 @@ pub fn writeFileAll(self: File, in_file: File, options: BufferedWriter.WriteFile...@@ -940,14 +941,27 @@ pub fn writeFileAll(self: File, in_file: File, options: BufferedWriter.WriteFile
940941
941pub const Reader = struct {942pub const Reader = struct {
942 file: File,943 file: File,
943 err: ReadError!void = {},944 err: ?ReadError = null,
944 mode: Reader.Mode = .positional,945 mode: Reader.Mode = .positional,
945 pos: u64 = 0,946 pos: u64 = 0,
946 size: ?u64 = null,947 size: ?u64 = null,
947 size_err: GetEndPosError!void = {},948 size_err: ?GetEndPosError = null,
948 seek_err: SeekError!void = {},949 seek_err: ?SeekError = null,
949950
950 pub const Mode = enum { streaming, positional };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
952 pub fn interface(r: *Reader) std.io.Reader {966 pub fn interface(r: *Reader) std.io.Reader {
953 return .{967 return .{
...@@ -975,7 +989,7 @@ pub const Reader = struct {...@@ -975,7 +989,7 @@ pub const Reader = struct {
975 switch (r.mode) {989 switch (r.mode) {
976 .positional => {990 .positional => {
977 const size = r.size orelse {991 const size = r.size orelse {
978 if (r.file.getEndPos()) |size| {992 if (file.getEndPos()) |size| {
979 r.size = size;993 r.size = size;
980 } else |err| {994 } else |err| {
981 r.size_err = err;995 r.size_err = err;
...@@ -991,6 +1005,10 @@ pub const Reader = struct {...@@ -991,6 +1005,10 @@ pub const Reader = struct {
991 assert(pos == 0);1005 assert(pos == 0);
992 return 0;1006 return 0;
993 },1007 },
1008 error.Unimplemented => {
1009 r.mode = .positional_reading;
1010 return 0;
1011 },
994 else => |e| {1012 else => |e| {
995 r.err = e;1013 r.err = e;
996 return error.ReadFailed;1014 return error.ReadFailed;
...@@ -1003,12 +1021,45 @@ pub const Reader = struct {...@@ -1003,12 +1021,45 @@ pub const Reader = struct {
1003 const n = bw.writeFile(file, .none, limit, &.{}, 0) catch |err| switch (err) {1021 const n = bw.writeFile(file, .none, limit, &.{}, 0) catch |err| switch (err) {
1004 error.WriteFailed => return error.WriteFailed,1022 error.WriteFailed => return error.WriteFailed,
1005 error.Unseekable => unreachable, // Passing `Offset.none`.1023 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 },
1006 else => |e| {1044 else => |e| {
1007 r.err = e;1045 r.err = e;
1008 return error.ReadFailed;1046 return error.ReadFailed;
1009 },1047 },
1010 };1048 };
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;
1011 r.pos = pos + n;1061 r.pos = pos + n;
1062 bw.advance(n);
1012 return n;1063 return n;
1013 },1064 },
1014 }1065 }
...@@ -1020,7 +1071,7 @@ pub const Reader = struct {...@@ -1020,7 +1071,7 @@ pub const Reader = struct {
1020 const pos = r.pos;1071 const pos = r.pos;
10211072
1022 switch (r.mode) {1073 switch (r.mode) {
1023 .positional => {1074 .positional, .positional_reading => {
1024 if (is_windows) {1075 if (is_windows) {
1025 // Unfortunately, `ReadFileScatter` cannot be used since it requires1076 // Unfortunately, `ReadFileScatter` cannot be used since it requires
1026 // page alignment, so we are stuck using only the first slice.1077 // page alignment, so we are stuck using only the first slice.
...@@ -1053,7 +1104,7 @@ pub const Reader = struct {...@@ -1053,7 +1104,7 @@ pub const Reader = struct {
1053 if (send_vecs.len == 0) return 0; // Prevent false positive end detection on empty `data`.1104 if (send_vecs.len == 0) return 0; // Prevent false positive end detection on empty `data`.
1054 const n = posix.preadv(handle, send_vecs, pos) catch |err| switch (err) {1105 const n = posix.preadv(handle, send_vecs, pos) catch |err| switch (err) {
1055 error.Unseekable => {1106 error.Unseekable => {
1056 r.mode = .streaming;1107 r.mode = r.mode.toStreaming();
1057 assert(pos == 0);1108 assert(pos == 0);
1058 return 0;1109 return 0;
1059 },1110 },
...@@ -1066,7 +1117,7 @@ pub const Reader = struct {...@@ -1066,7 +1117,7 @@ pub const Reader = struct {
1066 r.pos = pos + n;1117 r.pos = pos + n;
1067 return n;1118 return n;
1068 },1119 },
1069 .streaming => {1120 .streaming, .streaming_reading => {
1070 if (is_windows) {1121 if (is_windows) {
1071 // Unfortunately, `ReadFileScatter` cannot be used since it requires1122 // Unfortunately, `ReadFileScatter` cannot be used since it requires
1072 // page alignment, so we are stuck using only the first slice.1123 // page alignment, so we are stuck using only the first slice.
...@@ -1113,13 +1164,13 @@ pub const Reader = struct {...@@ -1113,13 +1164,13 @@ pub const Reader = struct {
1113 const file = r.file;1164 const file = r.file;
1114 const pos = r.pos;1165 const pos = r.pos;
1115 switch (r.mode) {1166 switch (r.mode) {
1116 .positional => {1167 .positional, .positional_reading => {
1117 const size = r.size orelse {1168 const size = r.size orelse {
1118 if (file.getEndPos()) |size| {1169 if (file.getEndPos()) |size| {
1119 r.size = size;1170 r.size = size;
1120 } else |err| {1171 } else |err| {
1121 r.size_err = err;1172 r.size_err = err;
1122 r.mode = .streaming;1173 r.mode = r.mode.toStreaming();
1123 }1174 }
1124 return 0;1175 return 0;
1125 };1176 };
...@@ -1127,17 +1178,13 @@ pub const Reader = struct {...@@ -1127,17 +1178,13 @@ pub const Reader = struct {
1127 r.pos = pos + delta;1178 r.pos = pos + delta;
1128 return delta;1179 return delta;
1129 },1180 },
1130 .streaming => {1181 .streaming, .streaming_reading => {
1131 // Unfortunately we can't seek forward without knowing the1182 // Unfortunately we can't seek forward without knowing the
1132 // size because the seek syscalls provided to us will not1183 // size because the seek syscalls provided to us will not
1133 // return the true end position if a seek would exceed the1184 // return the true end position if a seek would exceed the
1134 // end.1185 // end.
1135 fallback: {1186 fallback: {
1136 if (r.size_err) |_| {1187 if (r.size_err == null and r.seek_err == null) break :fallback;
1137 if (r.seek_err) |_| {
1138 break :fallback;
1139 } else |_| {}
1140 } else |_| {}
1141 var trash_buffer: [std.atomic.cache_line]u8 = undefined;1188 var trash_buffer: [std.atomic.cache_line]u8 = undefined;
1142 const trash = &trash_buffer;1189 const trash = &trash_buffer;
1143 if (is_windows) {1190 if (is_windows) {
...@@ -1188,9 +1235,16 @@ pub const Writer = struct {...@@ -1188,9 +1235,16 @@ pub const Writer = struct {
1188 err: WriteError!void = {},1235 err: WriteError!void = {},
1189 mode: Writer.Mode = .positional,1236 mode: Writer.Mode = .positional,
1190 pos: u64 = 0,1237 pos: u64 = 0,
1238 sendfile_err: ?SendfileError = null,
1239 read_err: ?ReadError = null,
11911240
1192 pub const Mode = Reader.Mode;1241 pub const Mode = Reader.Mode;
11931242
1243 pub const SendfileError = error{
1244 UnsupportedOperation,
1245 Unexpected,
1246 };
1247
1194 /// Number of slices to store on the stack, when trying to send as many byte1248 /// Number of slices to store on the stack, when trying to send as many byte
1195 /// vectors through the underlying write calls as possible.1249 /// vectors through the underlying write calls as possible.
1196 const max_buffers_len = 16;1250 const max_buffers_len = 16;
...@@ -1269,75 +1323,38 @@ pub const Writer = struct {...@@ -1269,75 +1323,38 @@ pub const Writer = struct {
1269 const w: *Writer = @ptrCast(@alignCast(context));1323 const w: *Writer = @ptrCast(@alignCast(context));
1270 const out_fd = w.file.handle;1324 const out_fd = w.file.handle;
1271 const in_fd = in_file.handle;1325 const in_fd = in_file.handle;
1272 const len_int = switch (in_limit) {1326 // TODO try using copy_file_range on Linux
1273 .nothing => return writeSplat(context, headers_and_trailers, 1),1327 // TODO try using copy_file_range on FreeBSD
1274 .unlimited => 0,1328 // TODO try using sendfile on macOS
1275 else => in_limit.toInt().?,1329 // TODO try using sendfile on FreeBSD
1276 };1330 if (native_os == .linux and w.mode == .streaming) sf: {
1277 // TODO try using copy_file_range on linux1331 // Try using sendfile on Linux.
1278 // TODO try using copy_file_range on freebsd1332 if (w.sendfile_err != null) break :sf;
1279 if (native_os == .linux) sf: {
1280 // Linux sendfile does not support headers or trailers but it does1333 // Linux sendfile does not support headers or trailers but it does
1281 // support a streaming read from in_file.1334 // support a streaming read from in_file.
1282 if (headers_len > 0) return writeSplat(context, headers_and_trailers[0..headers_len], 1);1335 if (headers_len > 0) return writeSplat(context, headers_and_trailers[0..headers_len], 1);
1283 const max_count = 0x7ffff000; // Avoid EINVAL.1336 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);
1285 var off: std.os.linux.off_t = undefined;1338 var off: std.os.linux.off_t = undefined;
1286 const off_ptr: ?*std.os.linux.off_t = if (in_offset.toInt()) |offset| b: {1339 const off_ptr: ?*std.os.linux.off_t = if (in_offset.toInt()) |offset| b: {
1287 off = std.math.cast(std.os.linux.off_t, offset) orelse1340 off = std.math.cast(std.os.linux.off_t, offset) orelse
1288 return writeSplat(context, headers_and_trailers, 1);1341 return writeSplat(context, headers_and_trailers, 1);
1289 break :b &off;1342 break :b &off;
1290 } else null;1343 } else null;
1291 if (true) @panic("TODO");
1292 const n = std.os.linux.wrapped.sendfile(out_fd, in_fd, off_ptr, smaller_len) catch |err| switch (err) {1344 const n = std.os.linux.wrapped.sendfile(out_fd, in_fd, off_ptr, smaller_len) catch |err| switch (err) {
1293 error.UnsupportedOperation => break :sf,1345 // Errors that imply sendfile should be avoided on the next write.
1294 error.Unseekable => break :sf,1346 error.UnsupportedOperation,
1295 error.Unexpected => break :sf,1347 error.Unexpected,
1348 => |e| {
1349 w.sendfile_err = e;
1350 break :sf;
1351 },
1296 else => |e| return e,1352 else => |e| return e,
1297 };1353 };
1298 if (in_offset.toInt()) |offset| {1354 w.pos += n;
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 }
1305 return n;1355 return n;
1306 }1356 }
1307 var iovecs_buffer: [max_buffers_len]std.posix.iovec_const = undefined;1357 return error.Unimplemented;
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");
1341 }1358 }
1342};1359};
13431360
lib/std/io/BufferedReader.zig+1-1
...@@ -99,7 +99,7 @@ pub fn readVecLimit(br: *BufferedReader, data: []const []u8, limit: Reader.Limit...@@ -99,7 +99,7 @@ pub fn readVecLimit(br: *BufferedReader, data: []const []u8, limit: Reader.Limit
9999
100fn passthruRead(context: ?*anyopaque, bw: *BufferedWriter, limit: Reader.Limit) Reader.RwError!usize {100fn passthruRead(context: ?*anyopaque, bw: *BufferedWriter, limit: Reader.Limit) Reader.RwError!usize {
101 const br: *BufferedReader = @alignCast(@ptrCast(context));101 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]);
103 if (buffer.len > 0) {103 if (buffer.len > 0) {
104 const n = try bw.write(buffer);104 const n = try bw.write(buffer);
105 br.seek += n;105 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...@@ -480,6 +480,14 @@ pub fn writeSliceSwap(bw: *BufferedWriter, Elem: type, slice: []const Elem) Writ
480/// Unlike `writeSplat` and `writeVec`, this function will call into the480/// Unlike `writeSplat` and `writeVec`, this function will call into the
481/// underlying writer even if there is enough buffer capacity for the file481/// underlying writer even if there is enough buffer capacity for the file
482/// contents.482/// 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.
483pub fn writeFile(491pub fn writeFile(
484 bw: *BufferedWriter,492 bw: *BufferedWriter,
485 file: std.fs.File,493 file: std.fs.File,
...@@ -491,6 +499,23 @@ pub fn writeFile(...@@ -491,6 +499,23 @@ pub fn writeFile(
491 return passthruWriteFile(bw, file, offset, limit, headers_and_trailers, headers_len);499 return passthruWriteFile(bw, file, offset, limit, headers_and_trailers, headers_len);
492}500}
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
494fn passthruWriteFile(519fn passthruWriteFile(
495 context: ?*anyopaque,520 context: ?*anyopaque,
496 file: std.fs.File,521 file: std.fs.File,
...@@ -588,7 +613,7 @@ pub const WriteFileOptions = struct {...@@ -588,7 +613,7 @@ pub const WriteFileOptions = struct {
588 headers_len: usize = 0,613 headers_len: usize = 0,
589};614};
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 {
592 const headers_and_trailers = options.headers_and_trailers;617 const headers_and_trailers = options.headers_and_trailers;
593 const headers = headers_and_trailers[0..options.headers_len];618 const headers = headers_and_trailers[0..options.headers_len];
594 switch (options.limit) {619 switch (options.limit) {
...@@ -601,7 +626,15 @@ pub fn writeFileAll(bw: *BufferedWriter, file: std.fs.File, options: WriteFileOp...@@ -601,7 +626,15 @@ pub fn writeFileAll(bw: *BufferedWriter, file: std.fs.File, options: WriteFileOp
601 var i: usize = 0;626 var i: usize = 0;
602 var offset = options.offset;627 var offset = options.offset;
603 while (true) {628 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 };
605 while (i < headers.len and n >= headers[i].len) {638 while (i < headers.len and n >= headers[i].len) {
606 n -= headers[i].len;639 n -= headers[i].len;
607 i += 1;640 i += 1;
...@@ -619,7 +652,15 @@ pub fn writeFileAll(bw: *BufferedWriter, file: std.fs.File, options: WriteFileOp...@@ -619,7 +652,15 @@ pub fn writeFileAll(bw: *BufferedWriter, file: std.fs.File, options: WriteFileOp
619 var i: usize = 0;652 var i: usize = 0;
620 var offset = options.offset;653 var offset = options.offset;
621 while (true) {654 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 };
623 while (i < headers.len and n >= headers[i].len) {664 while (i < headers.len and n >= headers[i].len) {
624 n -= headers[i].len;665 n -= headers[i].len;
625 i += 1;666 i += 1;
...@@ -646,6 +687,40 @@ pub fn writeFileAll(bw: *BufferedWriter, file: std.fs.File, options: WriteFileOp...@@ -646,6 +687,40 @@ pub fn writeFileAll(bw: *BufferedWriter, file: std.fs.File, options: WriteFileOp
646 }687 }
647}688}
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
649pub fn alignBuffer(724pub fn alignBuffer(
650 bw: *BufferedWriter,725 bw: *BufferedWriter,
651 buffer: []const u8,726 buffer: []const u8,
lib/std/io/Writer.zig+18-4
...@@ -30,12 +30,20 @@ pub const VTable = struct {...@@ -30,12 +30,20 @@ pub const VTable = struct {
30 /// Number of bytes returned may be zero, which does not mean30 /// Number of bytes returned may be zero, which does not mean
31 /// end-of-stream. A subsequent call may return nonzero, or may signal end31 /// end-of-stream. A subsequent call may return nonzero, or may signal end
32 /// of stream via `error.WriteFailed`.32 /// 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.
33 writeFile: *const fn (37 writeFile: *const fn (
34 ctx: ?*anyopaque,38 ctx: ?*anyopaque,
35 file: std.fs.File,39 file: std.fs.File,
36 /// If this is `none`, `file` will be streamed, affecting the seek40 /// If this is `Offset.none`, `file` will be streamed, affecting the
37 /// position. Otherwise, it will be read positionally without affecting41 /// seek position. Otherwise, it will be read positionally without
38 /// the seek position.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.
39 offset: Offset,47 offset: Offset,
40 /// Maximum amount of bytes to read from the file. Implementations may48 /// Maximum amount of bytes to read from the file. Implementations may
41 /// assume that the file size does not exceed this amount.49 /// assume that the file size does not exceed this amount.
...@@ -52,7 +60,13 @@ pub const Error = error{...@@ -52,7 +60,13 @@ pub const Error = error{
52 WriteFailed,60 WriteFailed,
53};61};
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
57pub const Limit = std.io.Reader.Limit;71pub const Limit = std.io.Reader.Limit;
5872
lib/std/os/linux.zig+64-1
...@@ -9420,4 +9420,67 @@ pub const msghdr_const = extern struct {...@@ -9420,4 +9420,67 @@ pub const msghdr_const = extern struct {
9420 control: ?*const anyopaque,9420 control: ?*const anyopaque,
9421 controllen: usize,9421 controllen: usize,
9422 flags: u32,9422 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 {...@@ -7553,7 +7553,7 @@ pub fn ioctl_SIOCGIFINDEX(fd: fd_t, ifr: *ifreq) IoCtl_SIOCGIFINDEX_Error!void {
7553 }7553 }
7554}7554}
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
7558/// Whether or not `error.Unexpected` will print its value and a stack trace.7558/// Whether or not `error.Unexpected` will print its value and a stack trace.
7559///7559///