authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-05 17:12:57-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-23 22:15:07-08:00
logba999d608c304c37667175b35e4a64f87d90351c
tree4557419b972c7d47aa1b3a79f8e1909f1bf9228a
parent814480db7cd0ab1d8441919223a88ee411f54ea8

std: extract sendfile/copy_file_range from Io.File.Writer

and move it into std.Io.Threaded (below the VTable)

11 files changed, 1008 insertions(+), 867 deletions(-)

lib/std/Io.zig+7-4
......@@ -662,8 +662,8 @@ pub const VTable = struct {
662662 futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void,
663663 futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void,
664664
665 dirMake: *const fn (?*anyopaque, Dir, []const u8, Dir.Mode) Dir.MakeError!void,
666 dirMakePath: *const fn (?*anyopaque, Dir, []const u8, Dir.Mode) Dir.MakePathError!Dir.MakePathStatus,
665 dirMake: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.MakeError!void,
666 dirMakePath: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.MakePathError!Dir.MakePathStatus,
667667 dirMakeOpenPath: *const fn (?*anyopaque, Dir, []const u8, Dir.OpenOptions) Dir.MakeOpenPathError!Dir,
668668 dirStat: *const fn (?*anyopaque, Dir) Dir.StatError!Dir.Stat,
669669 dirStatPath: *const fn (?*anyopaque, Dir, []const u8, Dir.StatPathOptions) Dir.StatPathError!File.Stat,
......@@ -687,8 +687,10 @@ pub const VTable = struct {
687687 fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat,
688688 fileLength: *const fn (?*anyopaque, File) File.LengthError!u64,
689689 fileClose: *const fn (?*anyopaque, File) void,
690 fileWriteStreaming: *const fn (?*anyopaque, File, buffer: [][]const u8) File.WriteStreamingError!usize,
691 fileWritePositional: *const fn (?*anyopaque, File, buffer: [][]const u8, offset: u64) File.WritePositionalError!usize,
690 fileWriteStreaming: *const fn (?*anyopaque, File, header: []const u8, data: []const []const u8, splat: usize) File.Writer.Error!usize,
691 fileWritePositional: *const fn (?*anyopaque, File, header: []const u8, data: []const []const u8, splat: usize, offset: u64) File.WritePositionalError!usize,
692 fileWriteFileStreaming: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit) File.Writer.WriteFileError!usize,
693 fileWriteFilePositional: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit, offset: u64) File.WriteFilePositionalError!usize,
692694 /// Returns 0 on end of stream.
693695 fileReadStreaming: *const fn (?*anyopaque, File, data: [][]u8) File.Reader.Error!usize,
694696 /// Returns 0 on end of stream.
......@@ -724,6 +726,7 @@ pub const VTable = struct {
724726 /// Returns 0 on end of stream.
725727 netRead: *const fn (?*anyopaque, src: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize,
726728 netWrite: *const fn (?*anyopaque, dest: net.Socket.Handle, header: []const u8, data: []const []const u8, splat: usize) net.Stream.Writer.Error!usize,
729 netWriteFile: *const fn (?*anyopaque, net.Socket.Handle, header: []const u8, *Io.File.Reader, Io.Limit) net.Stream.WriteFileError!usize,
727730 netClose: *const fn (?*anyopaque, handle: net.Socket.Handle) void,
728731 netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface,
729732 netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name,
lib/std/Io/Dir.zig-2
......@@ -652,7 +652,6 @@ pub const RealPathError = error{
652652 NoSpaceLeft,
653653 FileSystem,
654654 DeviceBusy,
655 ProcessNotFound,
656655 SharingViolation,
657656 PipeBusy,
658657 /// Windows: file paths provided by the user must be valid WTF-8.
......@@ -1038,7 +1037,6 @@ pub const DeleteTreeError = error{
10381037 FileSystem,
10391038 FileBusy,
10401039 DeviceBusy,
1041 ProcessNotFound,
10421040 /// One of the path components was not a directory.
10431041 /// This error is unreachable if `sub_path` does not contain a path separator.
10441042 NotDir,
lib/std/Io/File.zig+16-8
......@@ -198,7 +198,6 @@ pub const OpenError = error{
198198 NoDevice,
199199 /// On Windows, `\\server` or `\\server\share` was not found.
200200 NetworkNotFound,
201 ProcessNotFound,
202201 /// On Windows, antivirus software is enabled by default. It can be
203202 /// disabled, but Windows Update sometimes ignores the user's preference
204203 /// and re-enables it. When enabled, antivirus software on Windows
......@@ -477,18 +476,25 @@ pub fn readPositional(file: File, io: Io, buffer: [][]u8, offset: u64) ReadPosit
477476 return io.vtable.fileReadPositional(io.userdata, file, buffer, offset);
478477}
479478
480pub const WriteStreamingError = error{} || Io.UnexpectedError || Io.Cancelable;
481
482pub fn writeStreaming(file: File, io: Io, buffer: [][]const u8) WriteStreamingError!usize {
483 return file.fileWriteStreaming(io, buffer);
484}
485
486pub const WritePositionalError = WriteStreamingError || error{Unseekable};
479pub const WritePositionalError = Writer.Error || error{Unseekable};
487480
488481pub fn writePositional(file: File, io: Io, buffer: [][]const u8, offset: u64) WritePositionalError!usize {
489482 return io.vtable.fileWritePositional(io.userdata, file, buffer, offset);
490483}
491484
485pub const WriteFileStreamingError = error{
486 /// `out_fd` is an unconnected socket, or out_fd closed its read end.
487 BrokenPipe,
488 /// Descriptor is not valid or locked, or an mmap(2)-like operation is not available for in_fd.
489 UnsupportedOperation,
490 /// Nonblocking I/O has been selected but the write would block.
491 WouldBlock,
492 /// Unspecified error while reading from in_fd.
493 InputOutput,
494 /// Insufficient kernel memory to read from in_fd.
495 SystemResources,
496} || Io.Cancelable || Io.UnexpectedError;
497
492498/// Opens a file for reading or writing, without attempting to create a new
493499/// file, based on an absolute path.
494500///
......@@ -511,6 +517,8 @@ pub const SeekError = error{
511517 AccessDenied,
512518} || Io.Cancelable || Io.UnexpectedError;
513519
520pub const WriteFilePositionalError = Writer.WriteFileError || error{Unseekable};
521
514522/// Defaults to positional reading; falls back to streaming.
515523///
516524/// Positional is more threadsafe, since the global seek position is not
lib/std/Io/File/Reader.zig+11-14
......@@ -17,13 +17,13 @@ const assert = std.debug.assert;
1717io: Io,
1818file: File,
1919err: ?Error = null,
20mode: Reader.Mode = .positional,
20mode: Mode = .positional,
2121/// Tracks the true seek position in the file. To obtain the logical
2222/// position, use `logicalPos`.
2323pos: u64 = 0,
2424size: ?u64 = null,
2525size_err: ?SizeError = null,
26seek_err: ?Reader.SeekError = null,
26seek_err: ?SeekError = null,
2727interface: Io.Reader,
2828
2929pub const Error = error{
......@@ -37,15 +37,12 @@ pub const Error = error{
3737 /// trying to read a directory file descriptor as if it were a file.
3838 NotOpenForReading,
3939 SocketUnconnected,
40 /// This error occurs when no global event loop is configured,
41 /// and reading from the file descriptor would block.
40 /// Non-blocking has been enabled, and reading from the file descriptor
41 /// would block.
4242 WouldBlock,
4343 /// In WASI, this error occurs when the file descriptor does
4444 /// not hold the required rights to read from it.
4545 AccessDenied,
46 /// This error occurs in Linux if the process to be read from
47 /// no longer exists.
48 ProcessNotFound,
4946 /// Unable to read file due to lock.
5047 LockViolation,
5148} || Io.Cancelable || Io.UnexpectedError;
......@@ -93,9 +90,9 @@ pub const Mode = enum {
9390pub fn initInterface(buffer: []u8) Io.Reader {
9491 return .{
9592 .vtable = &.{
96 .stream = Reader.stream,
97 .discard = Reader.discard,
98 .readVec = Reader.readVec,
93 .stream = stream,
94 .discard = discard,
95 .readVec = readVec,
9996 },
10097 .buffer = buffer,
10198 .seek = 0,
......@@ -153,7 +150,7 @@ pub fn getSize(r: *Reader) SizeError!u64 {
153150 };
154151}
155152
156pub fn seekBy(r: *Reader, offset: i64) Reader.SeekError!void {
153pub fn seekBy(r: *Reader, offset: i64) SeekError!void {
157154 const io = r.io;
158155 switch (r.mode) {
159156 .positional, .positional_reading => {
......@@ -183,7 +180,7 @@ pub fn seekBy(r: *Reader, offset: i64) Reader.SeekError!void {
183180}
184181
185182/// Repositions logical read offset relative to the beginning of the file.
186pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void {
183pub fn seekTo(r: *Reader, offset: u64) SeekError!void {
187184 const io = r.io;
188185 switch (r.mode) {
189186 .positional, .positional_reading => {
......@@ -191,7 +188,7 @@ pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void {
191188 },
192189 .streaming, .streaming_reading => {
193190 const logical_pos = logicalPos(r);
194 if (offset >= logical_pos) return Reader.seekBy(r, @intCast(offset - logical_pos));
191 if (offset >= logical_pos) return seekBy(r, @intCast(offset - logical_pos));
195192 if (r.seek_err) |err| return err;
196193 io.vtable.fileSeekTo(io.userdata, r.file, offset) catch |err| {
197194 r.seek_err = err;
......@@ -224,7 +221,7 @@ fn stream(io_reader: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.Strea
224221 return streamMode(r, w, limit, r.mode);
225222}
226223
227pub fn streamMode(r: *Reader, w: *Io.Writer, limit: Io.Limit, mode: Reader.Mode) Io.Reader.StreamError!usize {
224pub fn streamMode(r: *Reader, w: *Io.Writer, limit: Io.Limit, mode: Mode) Io.Reader.StreamError!usize {
228225 switch (mode) {
229226 .positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) {
230227 error.Unimplemented => {
lib/std/Io/File/Writer.zig+100-438
......@@ -1,53 +1,38 @@
11const Writer = @This();
22
3const builtin = @import("builtin");
4const native_os = builtin.os.tag;
5const is_windows = native_os == .windows;
6
73const std = @import("../../std.zig");
84const Io = std.Io;
95const File = std.Io.File;
106const assert = std.debug.assert;
11const windows = std.os.windows;
12const posix = std.posix;
137
8io: Io,
149file: File,
1510err: ?File.WriteError = null,
16mode: Writer.Mode = .positional,
11mode: Mode = .positional,
1712/// Tracks the true seek position in the file. To obtain the logical
1813/// position, add the buffer size to this value.
1914pos: u64 = 0,
20sendfile_err: ?SendfileError = null,
21copy_file_range_err: ?CopyFileRangeError = null,
22fcopyfile_err: ?FcopyfileError = null,
23seek_err: ?Writer.SeekError = null,
15write_file_err: ?WriteFileError = null,
16seek_err: ?SeekError = null,
2417interface: Io.Writer,
2518
2619pub const Mode = File.Reader.Mode;
2720
28pub const SendfileError = error{
29 UnsupportedOperation,
30 SystemResources,
31 InputOutput,
21pub const WriteFileError = error{
22 /// `out_fd` is an unconnected socket, or out_fd closed its read end.
3223 BrokenPipe,
24 /// Descriptor is not valid or locked, or an mmap(2)-like operation is not available for in_fd.
25 UnsupportedOperation,
26 /// Nonblocking I/O has been selected but the write would block.
3327 WouldBlock,
34 Unexpected,
35};
36
37pub const CopyFileRangeError = std.os.freebsd.CopyFileRangeError || std.os.linux.wrapped.CopyFileRangeError;
38
39pub const FcopyfileError = error{
40 OperationNotSupported,
41 OutOfMemory,
42 Unexpected,
43};
28 /// Unspecified error while reading from in_fd.
29 InputOutput,
30 /// Insufficient kernel memory to read from in_fd.
31 SystemResources,
32} || Io.Cancelable || Io.UnexpectedError;
4433
4534pub const SeekError = Io.File.SeekError;
4635
47/// Number of slices to store on the stack, when trying to send as many byte
48/// vectors through the underlying write calls as possible.
49const max_buffers_len = 16;
50
5136pub fn init(file: File, buffer: []u8) Writer {
5237 return .{
5338 .file = file,
......@@ -91,445 +76,122 @@ pub fn moveToReader(w: *Writer) File.Reader {
9176
9277pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
9378 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
94 const handle = w.file.handle;
95 const buffered = io_w.buffered();
96 if (is_windows) switch (w.mode) {
97 .positional, .positional_reading => {
98 if (buffered.len != 0) {
99 const n = windows.WriteFile(handle, buffered, w.pos) catch |err| {
100 w.err = err;
101 return error.WriteFailed;
102 };
103 w.pos += n;
104 return io_w.consume(n);
105 }
106 for (data[0 .. data.len - 1]) |buf| {
107 if (buf.len == 0) continue;
108 const n = windows.WriteFile(handle, buf, w.pos) catch |err| {
109 w.err = err;
79 switch (w.mode) {
80 .positional, .positional_reading => return drainPositional(w, data, splat),
81 .streaming, .streaming_reading => return drainStreaming(w, data, splat),
82 .failure => return error.WriteFailed,
83 }
84}
85
86fn drainPositional(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
87 const io = w.io;
88 const header = w.interface.buffered();
89 const n = io.vtable.fileWritePositional(io.userdata, w.file, header, data, splat, w.pos) catch |err| switch (err) {
90 error.Unseekable => {
91 w.mode = w.mode.toStreaming();
92 const pos = w.pos;
93 if (pos != 0) {
94 w.pos = 0;
95 w.seekTo(@intCast(pos)) catch {
96 w.mode = .failure;
11097 return error.WriteFailed;
11198 };
112 w.pos += n;
113 return io_w.consume(n);
11499 }
115 const pattern = data[data.len - 1];
116 if (pattern.len == 0 or splat == 0) return 0;
117 const n = windows.WriteFile(handle, pattern, w.pos) catch |err| {
118 w.err = err;
119 return error.WriteFailed;
120 };
121 w.pos += n;
122 return io_w.consume(n);
100 return 0;
123101 },
124 .streaming, .streaming_reading => {
125 if (buffered.len != 0) {
126 const n = windows.WriteFile(handle, buffered, null) catch |err| {
127 w.err = err;
128 return error.WriteFailed;
129 };
130 w.pos += n;
131 return io_w.consume(n);
132 }
133 for (data[0 .. data.len - 1]) |buf| {
134 if (buf.len == 0) continue;
135 const n = windows.WriteFile(handle, buf, null) catch |err| {
136 w.err = err;
137 return error.WriteFailed;
138 };
139 w.pos += n;
140 return io_w.consume(n);
141 }
142 const pattern = data[data.len - 1];
143 if (pattern.len == 0 or splat == 0) return 0;
144 const n = windows.WriteFile(handle, pattern, null) catch |err| {
145 w.err = err;
146 return error.WriteFailed;
147 };
148 w.pos += n;
149 return io_w.consume(n);
102 else => |e| {
103 w.err = e;
104 return error.WriteFailed;
150105 },
151 .failure => return error.WriteFailed,
152106 };
153 var iovecs: [max_buffers_len]posix.iovec_const = undefined;
154 var len: usize = 0;
155 if (buffered.len > 0) {
156 iovecs[len] = .{ .base = buffered.ptr, .len = buffered.len };
157 len += 1;
158 }
159 for (data[0 .. data.len - 1]) |d| {
160 if (d.len == 0) continue;
161 iovecs[len] = .{ .base = d.ptr, .len = d.len };
162 len += 1;
163 if (iovecs.len - len == 0) break;
164 }
165 const pattern = data[data.len - 1];
166 if (iovecs.len - len != 0) switch (splat) {
167 0 => {},
168 1 => if (pattern.len != 0) {
169 iovecs[len] = .{ .base = pattern.ptr, .len = pattern.len };
170 len += 1;
171 },
172 else => switch (pattern.len) {
173 0 => {},
174 1 => {
175 const splat_buffer_candidate = io_w.buffer[io_w.end..];
176 var backup_buffer: [64]u8 = undefined;
177 const splat_buffer = if (splat_buffer_candidate.len >= backup_buffer.len)
178 splat_buffer_candidate
179 else
180 &backup_buffer;
181 const memset_len = @min(splat_buffer.len, splat);
182 const buf = splat_buffer[0..memset_len];
183 @memset(buf, pattern[0]);
184 iovecs[len] = .{ .base = buf.ptr, .len = buf.len };
185 len += 1;
186 var remaining_splat = splat - buf.len;
187 while (remaining_splat > splat_buffer.len and iovecs.len - len != 0) {
188 assert(buf.len == splat_buffer.len);
189 iovecs[len] = .{ .base = splat_buffer.ptr, .len = splat_buffer.len };
190 len += 1;
191 remaining_splat -= splat_buffer.len;
192 }
193 if (remaining_splat > 0 and iovecs.len - len != 0) {
194 iovecs[len] = .{ .base = splat_buffer.ptr, .len = remaining_splat };
195 len += 1;
196 }
197 },
198 else => for (0..splat) |_| {
199 iovecs[len] = .{ .base = pattern.ptr, .len = pattern.len };
200 len += 1;
201 if (iovecs.len - len == 0) break;
202 },
203 },
107 w.pos += n;
108 return w.interface.consume(n);
109}
110
111fn drainStreaming(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
112 const io = w.io;
113 const header = w.interface.buffered();
114 const n = io.vtable.fileWriteStreaming(io.userdata, w.file, header, data, splat) catch |err| {
115 w.err = err;
116 return error.WriteFailed;
204117 };
205 if (len == 0) return 0;
118 w.pos += n;
119 return w.interface.consume(n);
120}
121
122pub fn sendFile(io_w: *Io.Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize {
123 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
206124 switch (w.mode) {
207 .positional, .positional_reading => {
208 const n = posix.pwritev(handle, iovecs[0..len], w.pos) catch |err| switch (err) {
209 error.Unseekable => {
210 w.mode = w.mode.toStreaming();
211 const pos = w.pos;
212 if (pos != 0) {
213 w.pos = 0;
214 w.seekTo(@intCast(pos)) catch {
215 w.mode = .failure;
216 return error.WriteFailed;
217 };
218 }
219 return 0;
220 },
221 else => |e| {
222 w.err = e;
223 return error.WriteFailed;
224 },
225 };
226 w.pos += n;
227 return io_w.consume(n);
228 },
229 .streaming, .streaming_reading => {
230 const n = posix.writev(handle, iovecs[0..len]) catch |err| {
231 w.err = err;
232 return error.WriteFailed;
233 };
234 w.pos += n;
235 return io_w.consume(n);
236 },
125 .positional => return sendFilePositional(w, file_reader, limit),
126 .positional_reading => return error.Unimplemented,
127 .streaming => return sendFileStreaming(w, file_reader, limit),
128 .streaming_reading => return error.Unimplemented,
237129 .failure => return error.WriteFailed,
238130 }
239131}
240132
241pub fn sendFile(
242 io_w: *Io.Writer,
243 file_reader: *Io.File.Reader,
244 limit: Io.Limit,
245) Io.Writer.FileError!usize {
246 const reader_buffered = file_reader.interface.buffered();
247 if (reader_buffered.len >= @intFromEnum(limit))
248 return sendFileBuffered(io_w, file_reader, limit.slice(reader_buffered));
249 const writer_buffered = io_w.buffered();
250 const file_limit = @intFromEnum(limit) - reader_buffered.len;
251 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
252 const out_fd = w.file.handle;
253 const in_fd = file_reader.file.handle;
254
255 if (file_reader.size) |size| {
256 if (size - file_reader.pos == 0) {
257 if (reader_buffered.len != 0) {
258 return sendFileBuffered(io_w, file_reader, reader_buffered);
259 } else {
260 return error.EndOfStream;
133fn sendFilePositional(w: *Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize {
134 const io = w.io;
135 const header = w.interface.buffered();
136 const n = io.vtable.fileSendFilePositional(io.userdata, w.file, header, file_reader, limit, w.pos) catch |err| switch (err) {
137 error.Unseekable => {
138 w.mode = w.mode.toStreaming();
139 const pos = w.pos;
140 if (pos != 0) {
141 w.pos = 0;
142 w.seekTo(@intCast(pos)) catch {
143 w.mode = .failure;
144 return error.WriteFailed;
145 };
261146 }
262 }
263 }
264
265 if (native_os == .freebsd and w.mode == .streaming) sf: {
266 // Try using sendfile on FreeBSD.
267 if (w.sendfile_err != null) break :sf;
268 const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf;
269 var hdtr_data: std.c.sf_hdtr = undefined;
270 var headers: [2]posix.iovec_const = undefined;
271 var headers_i: u8 = 0;
272 if (writer_buffered.len != 0) {
273 headers[headers_i] = .{ .base = writer_buffered.ptr, .len = writer_buffered.len };
274 headers_i += 1;
275 }
276 if (reader_buffered.len != 0) {
277 headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len };
278 headers_i += 1;
279 }
280 const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: {
281 hdtr_data = .{
282 .headers = &headers,
283 .hdr_cnt = headers_i,
284 .trailers = null,
285 .trl_cnt = 0,
286 };
287 break :b &hdtr_data;
288 };
289 var sbytes: std.c.off_t = undefined;
290 const nbytes: usize = @min(file_limit, std.math.maxInt(usize));
291 const flags = 0;
292 switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, nbytes, hdtr, &sbytes, flags))) {
293 .SUCCESS, .INTR => {},
294 .INVAL, .OPNOTSUPP, .NOTSOCK, .NOSYS => w.sendfile_err = error.UnsupportedOperation,
295 .BADF => if (builtin.mode == .Debug) @panic("race condition") else {
296 w.sendfile_err = error.Unexpected;
297 },
298 .FAULT => if (builtin.mode == .Debug) @panic("segmentation fault") else {
299 w.sendfile_err = error.Unexpected;
300 },
301 .NOTCONN => w.sendfile_err = error.BrokenPipe,
302 .AGAIN, .BUSY => if (sbytes == 0) {
303 w.sendfile_err = error.WouldBlock;
304 },
305 .IO => w.sendfile_err = error.InputOutput,
306 .PIPE => w.sendfile_err = error.BrokenPipe,
307 .NOBUFS => w.sendfile_err = error.SystemResources,
308 else => |err| w.sendfile_err = posix.unexpectedErrno(err),
309 }
310 if (w.sendfile_err != null) {
311 // Give calling code chance to observe the error before trying
312 // something else.
313 return 0;
314 }
315 if (sbytes == 0) {
316 file_reader.size = file_reader.pos;
317 return error.EndOfStream;
318 }
319 const consumed = io_w.consume(@intCast(sbytes));
320 file_reader.seekBy(@intCast(consumed)) catch return error.ReadFailed;
321 return consumed;
322 }
323
324 if (native_os.isDarwin() and w.mode == .streaming) sf: {
325 // Try using sendfile on macOS.
326 if (w.sendfile_err != null) break :sf;
327 const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf;
328 var hdtr_data: std.c.sf_hdtr = undefined;
329 var headers: [2]posix.iovec_const = undefined;
330 var headers_i: u8 = 0;
331 if (writer_buffered.len != 0) {
332 headers[headers_i] = .{ .base = writer_buffered.ptr, .len = writer_buffered.len };
333 headers_i += 1;
334 }
335 if (reader_buffered.len != 0) {
336 headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len };
337 headers_i += 1;
338 }
339 const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: {
340 hdtr_data = .{
341 .headers = &headers,
342 .hdr_cnt = headers_i,
343 .trailers = null,
344 .trl_cnt = 0,
345 };
346 break :b &hdtr_data;
347 };
348 const max_count = std.math.maxInt(i32); // Avoid EINVAL.
349 var len: std.c.off_t = @min(file_limit, max_count);
350 const flags = 0;
351 switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, &len, hdtr, flags))) {
352 .SUCCESS, .INTR => {},
353 .OPNOTSUPP, .NOTSOCK, .NOSYS => w.sendfile_err = error.UnsupportedOperation,
354 .BADF => if (builtin.mode == .Debug) @panic("race condition") else {
355 w.sendfile_err = error.Unexpected;
356 },
357 .FAULT => if (builtin.mode == .Debug) @panic("segmentation fault") else {
358 w.sendfile_err = error.Unexpected;
359 },
360 .INVAL => if (builtin.mode == .Debug) @panic("invalid API usage") else {
361 w.sendfile_err = error.Unexpected;
362 },
363 .NOTCONN => w.sendfile_err = error.BrokenPipe,
364 .AGAIN => if (len == 0) {
365 w.sendfile_err = error.WouldBlock;
366 },
367 .IO => w.sendfile_err = error.InputOutput,
368 .PIPE => w.sendfile_err = error.BrokenPipe,
369 else => |err| w.sendfile_err = posix.unexpectedErrno(err),
370 }
371 if (w.sendfile_err != null) {
372 // Give calling code chance to observe the error before trying
373 // something else.
374147 return 0;
375 }
376 if (len == 0) {
377 file_reader.size = file_reader.pos;
378 return error.EndOfStream;
379 }
380 const consumed = io_w.consume(@bitCast(len));
381 file_reader.seekBy(@intCast(consumed)) catch return error.ReadFailed;
382 return consumed;
383 }
384
385 if (native_os == .linux and w.mode == .streaming) sf: {
386 // Try using sendfile on Linux.
387 if (w.sendfile_err != null) break :sf;
388 // Linux sendfile does not support headers.
389 if (writer_buffered.len != 0 or reader_buffered.len != 0)
390 return sendFileBuffered(io_w, file_reader, reader_buffered);
391 const max_count = 0x7ffff000; // Avoid EINVAL.
392 var off: std.os.linux.off_t = undefined;
393 const off_ptr: ?*std.os.linux.off_t, const count: usize = switch (file_reader.mode) {
394 .positional => o: {
395 const size = file_reader.getSize() catch return 0;
396 off = std.math.cast(std.os.linux.off_t, file_reader.pos) orelse return error.ReadFailed;
397 break :o .{ &off, @min(@intFromEnum(limit), size - file_reader.pos, max_count) };
398 },
399 .streaming => .{ null, limit.minInt(max_count) },
400 .streaming_reading, .positional_reading => break :sf,
401 .failure => return error.ReadFailed,
402 };
403 const n = std.os.linux.wrapped.sendfile(out_fd, in_fd, off_ptr, count) catch |err| switch (err) {
404 error.Unseekable => {
405 file_reader.mode = file_reader.mode.toStreaming();
406 const pos = file_reader.pos;
407 if (pos != 0) {
408 file_reader.pos = 0;
409 file_reader.seekBy(@intCast(pos)) catch {
410 file_reader.mode = .failure;
411 return error.ReadFailed;
412 };
413 }
414 return 0;
415 },
416 else => |e| {
417 w.sendfile_err = e;
418 return 0;
419 },
420 };
421 if (n == 0) {
422 file_reader.size = file_reader.pos;
423 return error.EndOfStream;
424 }
425 file_reader.pos += n;
426 w.pos += n;
427 return n;
428 }
429
430 const copy_file_range = switch (native_os) {
431 .freebsd => std.os.freebsd.copy_file_range,
432 .linux => std.os.linux.wrapped.copy_file_range,
433 else => {},
148 },
149 error.Canceled => {
150 w.err = error.Canceled;
151 return error.WriteFailed;
152 },
153 else => |e| {
154 w.write_file_err = e;
155 return error.WriteFailed;
156 },
434157 };
435 if (@TypeOf(copy_file_range) != void) cfr: {
436 if (w.copy_file_range_err != null) break :cfr;
437 if (writer_buffered.len != 0 or reader_buffered.len != 0)
438 return sendFileBuffered(io_w, file_reader, reader_buffered);
439 var off_in: i64 = undefined;
440 var off_out: i64 = undefined;
441 const off_in_ptr: ?*i64 = switch (file_reader.mode) {
442 .positional_reading, .streaming_reading => return error.Unimplemented,
443 .positional => p: {
444 off_in = @intCast(file_reader.pos);
445 break :p &off_in;
446 },
447 .streaming => null,
448 .failure => return error.WriteFailed,
449 };
450 const off_out_ptr: ?*i64 = switch (w.mode) {
451 .positional_reading, .streaming_reading => return error.Unimplemented,
452 .positional => p: {
453 off_out = @intCast(w.pos);
454 break :p &off_out;
455 },
456 .streaming => null,
457 .failure => return error.WriteFailed,
458 };
459 const n = copy_file_range(in_fd, off_in_ptr, out_fd, off_out_ptr, @intFromEnum(limit), 0) catch |err| {
460 w.copy_file_range_err = err;
461 return 0;
462 };
463 if (n == 0) {
464 file_reader.size = file_reader.pos;
465 return error.EndOfStream;
466 }
467 file_reader.pos += n;
468 w.pos += n;
469 return n;
470 }
471
472 if (builtin.os.tag.isDarwin()) fcf: {
473 if (w.fcopyfile_err != null) break :fcf;
474 if (file_reader.pos != 0) break :fcf;
475 if (w.pos != 0) break :fcf;
476 if (limit != .unlimited) break :fcf;
477 const size = file_reader.getSize() catch break :fcf;
478 if (writer_buffered.len != 0 or reader_buffered.len != 0)
479 return sendFileBuffered(io_w, file_reader, reader_buffered);
480 const rc = std.c.fcopyfile(in_fd, out_fd, null, .{ .DATA = true });
481 switch (posix.errno(rc)) {
482 .SUCCESS => {},
483 .INVAL => if (builtin.mode == .Debug) @panic("invalid API usage") else {
484 w.fcopyfile_err = error.Unexpected;
485 return 0;
486 },
487 .NOMEM => {
488 w.fcopyfile_err = error.OutOfMemory;
489 return 0;
490 },
491 .OPNOTSUPP => {
492 w.fcopyfile_err = error.OperationNotSupported;
493 return 0;
494 },
495 else => |err| {
496 w.fcopyfile_err = posix.unexpectedErrno(err);
497 return 0;
498 },
499 }
500 file_reader.pos = size;
501 w.pos = size;
502 return size;
503 }
504
505 return error.Unimplemented;
158 w.pos += n;
159 return w.interface.consume(n);
506160}
507161
508fn sendFileBuffered(
509 io_w: *Io.Writer,
510 file_reader: *Io.File.Reader,
511 reader_buffered: []const u8,
512) Io.Writer.FileError!usize {
513 const n = try drain(io_w, &.{reader_buffered}, 1);
514 file_reader.seekBy(@intCast(n)) catch return error.ReadFailed;
515 return n;
162fn sendFileStreaming(w: *Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize {
163 const io = w.io;
164 const header = w.interface.buffered();
165 const n = io.vtable.fileSendFileStreaming(io.userdata, w.file, header, file_reader, limit) catch |err| switch (err) {
166 error.Canceled => {
167 w.err = error.Canceled;
168 return error.WriteFailed;
169 },
170 else => |e| {
171 w.write_file_err = e;
172 return error.WriteFailed;
173 },
174 };
175 w.pos += n;
176 return w.interface.consume(n);
516177}
517178
518pub fn seekTo(w: *Writer, offset: u64) (Writer.SeekError || Io.Writer.Error)!void {
179pub fn seekTo(w: *Writer, offset: u64) (SeekError || Io.Writer.Error)!void {
519180 try w.interface.flush();
520181 try seekToUnbuffered(w, offset);
521182}
522183
523184/// Asserts that no data is currently buffered.
524pub fn seekToUnbuffered(w: *Writer, offset: u64) Writer.SeekError!void {
185pub fn seekToUnbuffered(w: *Writer, offset: u64) SeekError!void {
525186 assert(w.interface.buffered().len == 0);
187 const io = w.io;
526188 switch (w.mode) {
527189 .positional, .positional_reading => {
528190 w.pos = offset;
529191 },
530192 .streaming, .streaming_reading => {
531193 if (w.seek_err) |err| return err;
532 posix.lseek_SET(w.file.handle, offset) catch |err| {
194 io.vtable.fileSeekTo(io.userdata, w.file, offset) catch |err| {
533195 w.seek_err = err;
534196 return err;
535197 };
......@@ -553,7 +215,7 @@ pub fn end(w: *Writer) EndError!void {
553215 switch (w.mode) {
554216 .positional,
555217 .positional_reading,
556 => w.file.setEndPos(w.pos) catch |err| switch (err) {
218 => w.file.setLength(w.pos) catch |err| switch (err) {
557219 error.NonResizable => return,
558220 else => |e| return e,
559221 },
lib/std/Io/Threaded.zig+874-14
......@@ -3,6 +3,7 @@ const Threaded = @This();
33const builtin = @import("builtin");
44const native_os = builtin.os.tag;
55const is_windows = native_os == .windows;
6const is_darwin = native_os.isDarwin();
67const windows = std.os.windows;
78const ws2_32 = std.os.windows.ws2_32;
89const is_debug = builtin.mode == .Debug;
......@@ -70,6 +71,10 @@ have_signal_handler: bool,
7071old_sig_io: if (have_sig_io) posix.Sigaction else void,
7172old_sig_pipe: if (have_sig_pipe) posix.Sigaction else void,
7273
74use_sendfile: UseSendfile = .default,
75use_copy_file_range: UseCopyFileRange = .default,
76use_fcopyfile: UseFcopyfile = .default,
77
7378pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum {
7479 enabled,
7580 disabled,
......@@ -82,6 +87,33 @@ pub const Pid = if (native_os == .linux) enum(posix.pid_t) {
8287 _,
8388} else enum(u0) { unknown = 0 };
8489
90pub const UseSendfile = if (have_sendfile) enum {
91 enabled,
92 disabled,
93 pub const default: UseSendfile = .enabled;
94} else enum {
95 disabled,
96 pub const default: UseSendfile = .disabled;
97};
98
99pub const UseCopyFileRange = if (have_copy_file_range) enum {
100 enabled,
101 disabled,
102 pub const default: UseCopyFileRange = .enabled;
103} else enum {
104 disabled,
105 pub const default: UseCopyFileRange = .disabled;
106};
107
108pub const UseFcopyfile = if (have_fcopyfile) enum {
109 enabled,
110 disabled,
111 pub const default: UseFcopyfile = .enabled;
112} else enum {
113 disabled,
114 pub const default: UseFcopyfile = .disabled;
115};
116
85117const Thread = struct {
86118 /// The value that needs to be passed to pthread_kill or tgkill in order to
87119 /// send a signal.
......@@ -677,6 +709,8 @@ pub fn io(t: *Threaded) Io {
677709 .fileClose = fileClose,
678710 .fileWriteStreaming = fileWriteStreaming,
679711 .fileWritePositional = fileWritePositional,
712 .fileWriteFileStreaming = fileWriteFileStreaming,
713 .fileWriteFilePositional = fileWriteFilePositional,
680714 .fileReadStreaming = fileReadStreaming,
681715 .fileReadPositional = fileReadPositional,
682716 .fileSeekBy = fileSeekBy,
......@@ -797,6 +831,8 @@ pub fn ioBasic(t: *Threaded) Io {
797831 .fileClose = fileClose,
798832 .fileWriteStreaming = fileWriteStreaming,
799833 .fileWritePositional = fileWritePositional,
834 .fileWriteFileStreaming = fileWriteFileStreaming,
835 .fileWriteFilePositional = fileWriteFilePositional,
800836 .fileReadStreaming = fileReadStreaming,
801837 .fileReadPositional = fileReadPositional,
802838 .fileSeekBy = fileSeekBy,
......@@ -837,7 +873,7 @@ pub fn ioBasic(t: *Threaded) Io {
837873 };
838874}
839875
840pub const socket_flags_unsupported = native_os.isDarwin() or native_os == .haiku;
876pub const socket_flags_unsupported = is_darwin or native_os == .haiku;
841877const have_accept4 = !socket_flags_unsupported;
842878const have_flock_open_flags = @hasField(posix.O, "EXLOCK");
843879const have_networking = native_os != .wasi;
......@@ -853,6 +889,12 @@ const have_preadv = switch (native_os) {
853889};
854890const have_sig_io = posix.SIG != void and @hasField(posix.SIG, "IO");
855891const have_sig_pipe = posix.SIG != void and @hasField(posix.SIG, "PIPE");
892const have_sendfile = if (builtin.link_libc) @TypeOf(std.c.sendfile) != void else native_os == .linux;
893const have_copy_file_range = switch (native_os) {
894 .linux, .freebsd => true,
895 else => false,
896};
897const have_fcopyfile = is_darwin;
856898
857899const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat;
858900const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat;
......@@ -860,6 +902,18 @@ const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.syste
860902const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek;
861903const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv;
862904const ftruncate_sym = if (posix.lfs64_abi) posix.system.ftruncate64 else posix.system.ftruncate;
905const pwritev_sym = if (posix.lfs64_abi) posix.system.pwritev64 else posix.system.pwritev;
906const sendfile_sym = if (posix.lfs64_abi) posix.system.sendfile64 else posix.system.sendfile;
907const linux_copy_file_range_use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) .{
908 .major = 34,
909 .minor = 0,
910 .patch = 0,
911} else .{
912 .major = 2,
913 .minor = 27,
914 .patch = 0,
915});
916const linux_copy_file_range_sys = if (linux_copy_file_range_use_c) std.c else std.os.linux;
863917
864918/// Trailing data:
865919/// 1. context
......@@ -5896,27 +5950,828 @@ fn openSelfExe(userdata: ?*anyopaque, flags: Io.File.OpenFlags) Io.File.OpenSelf
58965950fn fileWritePositional(
58975951 userdata: ?*anyopaque,
58985952 file: Io.File,
5899 buffer: [][]const u8,
5953 header: []const u8,
5954 data: []const []const u8,
5955 splat: usize,
59005956 offset: u64,
59015957) Io.File.WritePositionalError!usize {
59025958 const t: *Threaded = @ptrCast(@alignCast(userdata));
5903 _ = t;
5959 const current_thread = Thread.getCurrent(t);
5960
5961 if (is_windows) @panic("TODO");
5962
5963 var iovecs: [max_iovecs_len]posix.iovec_const = undefined;
5964 var iovlen: iovlen_t = 0;
5965 addBuf(&iovecs, &iovlen, header);
5966 for (data[0 .. data.len - 1]) |bytes| addBuf(&iovecs, &iovlen, bytes);
5967 const pattern = data[data.len - 1];
5968 if (iovecs.len - iovlen != 0) switch (splat) {
5969 0 => {},
5970 1 => addBuf(&iovecs, &iovlen, pattern),
5971 else => switch (pattern.len) {
5972 0 => {},
5973 1 => {
5974 var backup_buffer: [splat_buffer_size]u8 = undefined;
5975 const splat_buffer = &backup_buffer;
5976 const memset_len = @min(splat_buffer.len, splat);
5977 const buf = splat_buffer[0..memset_len];
5978 @memset(buf, pattern[0]);
5979 addBuf(&iovecs, &iovlen, buf);
5980 var remaining_splat = splat - buf.len;
5981 while (remaining_splat > splat_buffer.len and iovecs.len - iovlen != 0) {
5982 assert(buf.len == splat_buffer.len);
5983 addBuf(&iovecs, &iovlen, splat_buffer);
5984 remaining_splat -= splat_buffer.len;
5985 }
5986 addBuf(&iovecs, &iovlen, splat_buffer[0..remaining_splat]);
5987 },
5988 else => for (0..@min(splat, iovecs.len - iovlen)) |_| {
5989 addBuf(&iovecs, &iovlen, pattern);
5990 },
5991 },
5992 };
5993
5994 if (native_os == .wasi and !builtin.link_libc) {
5995 var n_written: usize = undefined;
5996 try current_thread.beginSyscall();
5997 while (true) {
5998 switch (std.os.wasi.fd_pwrite(file.handle, &iovecs, iovlen, offset, &n_written)) {
5999 .SUCCESS => {
6000 current_thread.endSyscall();
6001 return n_written;
6002 },
6003 .INTR => {
6004 try current_thread.checkCancel();
6005 continue;
6006 },
6007 .CANCELED => return current_thread.endSyscallCanceled(),
6008 else => |e| {
6009 current_thread.endSyscall();
6010 switch (e) {
6011 .INVAL => |err| return errnoBug(err),
6012 .FAULT => |err| return errnoBug(err),
6013 .AGAIN => |err| return errnoBug(err),
6014 .BADF => return error.NotOpenForWriting, // can be a race condition.
6015 .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called.
6016 .DQUOT => return error.DiskQuota,
6017 .FBIG => return error.FileTooBig,
6018 .IO => return error.InputOutput,
6019 .NOSPC => return error.NoSpaceLeft,
6020 .PERM => return error.PermissionDenied,
6021 .PIPE => return error.BrokenPipe,
6022 .NOTCAPABLE => return error.AccessDenied,
6023 .NXIO => return error.Unseekable,
6024 .SPIPE => return error.Unseekable,
6025 .OVERFLOW => return error.Unseekable,
6026 else => |err| return posix.unexpectedErrno(err),
6027 }
6028 },
6029 }
6030 }
6031 }
6032
6033 try current_thread.beginSyscall();
59046034 while (true) {
5905 _ = file;
5906 _ = buffer;
5907 _ = offset;
5908 @panic("TODO implement fileWritePositional");
6035 const rc = pwritev_sym(file.handle, &iovecs, iovlen, @bitCast(offset));
6036 switch (posix.errno(rc)) {
6037 .SUCCESS => {
6038 current_thread.endSyscall();
6039 return @intCast(rc);
6040 },
6041 .INTR => {
6042 try current_thread.checkCancel();
6043 continue;
6044 },
6045 .CANCELED => return current_thread.endSyscallCanceled(),
6046 else => |e| {
6047 current_thread.endSyscall();
6048 switch (e) {
6049 .INVAL => return error.InvalidArgument,
6050 .FAULT => |err| return errnoBug(err),
6051 .AGAIN => return error.WouldBlock,
6052 .BADF => return error.NotOpenForWriting, // Usually a race condition.
6053 .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called.
6054 .DQUOT => return error.DiskQuota,
6055 .FBIG => return error.FileTooBig,
6056 .IO => return error.InputOutput,
6057 .NOSPC => return error.NoSpaceLeft,
6058 .PERM => return error.PermissionDenied,
6059 .PIPE => return error.BrokenPipe,
6060 .CONNRESET => return error.ConnectionResetByPeer,
6061 .BUSY => return error.DeviceBusy,
6062 .NXIO => return error.Unseekable,
6063 .SPIPE => return error.Unseekable,
6064 .OVERFLOW => return error.Unseekable,
6065 else => |err| return posix.unexpectedErrno(err),
6066 }
6067 },
6068 }
59096069 }
59106070}
59116071
5912fn fileWriteStreaming(userdata: ?*anyopaque, file: Io.File, buffer: [][]const u8) Io.File.WriteStreamingError!usize {
6072fn fileWriteStreaming(
6073 userdata: ?*anyopaque,
6074 file: Io.File,
6075 header: []const u8,
6076 data: []const []const u8,
6077 splat: usize,
6078) Io.File.WriteStreamingError!usize {
59136079 const t: *Threaded = @ptrCast(@alignCast(userdata));
5914 _ = t;
6080 const current_thread = Thread.getCurrent(t);
6081
6082 if (is_windows) @panic("TODO");
6083
6084 var iovecs: [max_iovecs_len]posix.iovec_const = undefined;
6085 var iovlen: iovlen_t = 0;
6086 addBuf(&iovecs, &iovlen, header);
6087 for (data[0 .. data.len - 1]) |bytes| addBuf(&iovecs, &iovlen, bytes);
6088 const pattern = data[data.len - 1];
6089 if (iovecs.len - iovlen != 0) switch (splat) {
6090 0 => {},
6091 1 => addBuf(&iovecs, &iovlen, pattern),
6092 else => switch (pattern.len) {
6093 0 => {},
6094 1 => {
6095 var backup_buffer: [splat_buffer_size]u8 = undefined;
6096 const splat_buffer = &backup_buffer;
6097 const memset_len = @min(splat_buffer.len, splat);
6098 const buf = splat_buffer[0..memset_len];
6099 @memset(buf, pattern[0]);
6100 addBuf(&iovecs, &iovlen, buf);
6101 var remaining_splat = splat - buf.len;
6102 while (remaining_splat > splat_buffer.len and iovecs.len - iovlen != 0) {
6103 assert(buf.len == splat_buffer.len);
6104 addBuf(&iovecs, &iovlen, splat_buffer);
6105 remaining_splat -= splat_buffer.len;
6106 }
6107 addBuf(&iovecs, &iovlen, splat_buffer[0..remaining_splat]);
6108 },
6109 else => for (0..@min(splat, iovecs.len - iovlen)) |_| {
6110 addBuf(&iovecs, &iovlen, pattern);
6111 },
6112 },
6113 };
6114
6115 if (native_os == .wasi and !builtin.link_libc) {
6116 var n_written: usize = undefined;
6117 try current_thread.beginSyscall();
6118 while (true) {
6119 switch (std.os.wasi.fd_write(file.handle, &iovecs, iovlen, &n_written)) {
6120 .SUCCESS => {
6121 current_thread.endSyscall();
6122 return n_written;
6123 },
6124 .INTR => {
6125 try current_thread.checkCancel();
6126 continue;
6127 },
6128 .CANCELED => return current_thread.endSyscallCanceled(),
6129 else => |e| {
6130 current_thread.endSyscall();
6131 switch (e) {
6132 .INVAL => |err| return errnoBug(err),
6133 .FAULT => |err| return errnoBug(err),
6134 .AGAIN => |err| return errnoBug(err),
6135 .BADF => return error.NotOpenForWriting, // can be a race condition.
6136 .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called.
6137 .DQUOT => return error.DiskQuota,
6138 .FBIG => return error.FileTooBig,
6139 .IO => return error.InputOutput,
6140 .NOSPC => return error.NoSpaceLeft,
6141 .PERM => return error.PermissionDenied,
6142 .PIPE => return error.BrokenPipe,
6143 .NOTCAPABLE => return error.AccessDenied,
6144 else => |err| return posix.unexpectedErrno(err),
6145 }
6146 },
6147 }
6148 }
6149 }
6150
6151 try current_thread.beginSyscall();
59156152 while (true) {
5916 _ = file;
5917 _ = buffer;
5918 @panic("TODO implement fileWriteStreaming");
6153 const rc = posix.system.writev(file.handle, &iovecs, iovlen);
6154 switch (posix.errno(rc)) {
6155 .SUCCESS => {
6156 current_thread.endSyscall();
6157 return @intCast(rc);
6158 },
6159 .INTR => {
6160 try current_thread.checkCancel();
6161 continue;
6162 },
6163 .CANCELED => return current_thread.endSyscallCanceled(),
6164 else => |e| {
6165 current_thread.endSyscall();
6166 switch (e) {
6167 .INVAL => return error.InvalidArgument,
6168 .FAULT => |err| return errnoBug(err),
6169 .SRCH => return error.ProcessNotFound,
6170 .AGAIN => return error.WouldBlock,
6171 .BADF => return error.NotOpenForWriting, // Can be a race condition.
6172 .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called.
6173 .DQUOT => return error.DiskQuota,
6174 .FBIG => return error.FileTooBig,
6175 .IO => return error.InputOutput,
6176 .NOSPC => return error.NoSpaceLeft,
6177 .PERM => return error.PermissionDenied,
6178 .PIPE => return error.BrokenPipe,
6179 .CONNRESET => return error.ConnectionResetByPeer,
6180 .BUSY => return error.DeviceBusy,
6181 else => |err| return posix.unexpectedErrno(err),
6182 }
6183 },
6184 }
6185 }
6186}
6187
6188fn fileWriteFileStreaming(
6189 userdata: ?*anyopaque,
6190 file: Io.File,
6191 header: []const u8,
6192 file_reader: *Io.File.Reader,
6193 limit: Io.Limit,
6194) Io.File.WriteFileStreamingError!usize {
6195 const t: *Threaded = @ptrCast(@alignCast(userdata));
6196 const reader_buffered = file_reader.interface.buffered();
6197 if (reader_buffered.len >= @intFromEnum(limit)) {
6198 const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1);
6199 file_reader.seekBy(n -| header.len) catch return error.ReadFailed;
6200 return n;
6201 }
6202 const file_limit = @intFromEnum(limit) - reader_buffered.len;
6203 const out_fd = file.handle;
6204 const in_fd = file_reader.file.handle;
6205
6206 if (file_reader.size) |size| {
6207 if (size - file_reader.pos == 0) {
6208 if (reader_buffered.len != 0) {
6209 const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1);
6210 file_reader.seekBy(n -| header.len) catch return error.ReadFailed;
6211 return n;
6212 } else {
6213 return error.EndOfStream;
6214 }
6215 }
6216 }
6217
6218 if (native_os == .freebsd) sf: {
6219 // Try using sendfile on FreeBSD.
6220 if (@atomicLoad(UseSendfile, &t.use_sendfile, .monotonic) == .disabled) break :sf;
6221 const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf;
6222 var hdtr_data: std.c.sf_hdtr = undefined;
6223 var headers: [2]posix.iovec_const = undefined;
6224 var headers_i: u8 = 0;
6225 if (header.len != 0) {
6226 headers[headers_i] = .{ .base = header.ptr, .len = header.len };
6227 headers_i += 1;
6228 }
6229 if (reader_buffered.len != 0) {
6230 headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len };
6231 headers_i += 1;
6232 }
6233 const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: {
6234 hdtr_data = .{
6235 .headers = &headers,
6236 .hdr_cnt = headers_i,
6237 .trailers = null,
6238 .trl_cnt = 0,
6239 };
6240 break :b &hdtr_data;
6241 };
6242 var sbytes: std.c.off_t = 0;
6243 const nbytes: usize = @min(file_limit, std.math.maxInt(usize));
6244 const flags = 0;
6245
6246 const current_thread = Thread.getCurrent(t);
6247 try current_thread.beginSyscall();
6248 while (true) {
6249 switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, nbytes, hdtr, &sbytes, flags))) {
6250 .SUCCESS => {
6251 current_thread.endSyscall();
6252 break;
6253 },
6254 .INVAL, .OPNOTSUPP, .NOTSOCK, .NOSYS => {
6255 // Give calling code chance to observe before trying
6256 // something else.
6257 current_thread.endSyscall();
6258 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic);
6259 return 0;
6260 },
6261 .INTR, .BUSY => {
6262 if (sbytes == 0) {
6263 try current_thread.checkCancel();
6264 continue;
6265 } else {
6266 // Even if we are being canceled, there have been side
6267 // effects, so it is better to report those side
6268 // effects to the caller.
6269 current_thread.endSyscall();
6270 break;
6271 }
6272 },
6273 .AGAIN => {
6274 current_thread.endSyscall();
6275 if (sbytes == 0) return error.WouldBlock;
6276 break;
6277 },
6278 else => |e| {
6279 current_thread.endSyscall();
6280 assert(error.Unexpected == switch (e) {
6281 .NOTCONN => return error.BrokenPipe,
6282 .IO => return error.InputOutput,
6283 .PIPE => return error.BrokenPipe,
6284 .NOBUFS => return error.SystemResources,
6285 .BADF => |err| errnoBug(err),
6286 .FAULT => |err| errnoBug(err),
6287 else => |err| posix.unexpectedErrno(err),
6288 });
6289 // Give calling code chance to observe the error before trying
6290 // something else.
6291 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic);
6292 return 0;
6293 },
6294 }
6295 }
6296 if (sbytes == 0) {
6297 file_reader.size = file_reader.pos;
6298 return error.EndOfStream;
6299 }
6300 const ubytes: usize = @intCast(sbytes);
6301 file_reader.seekBy(ubytes -| header.len) catch return error.ReadFailed;
6302 return ubytes;
6303 }
6304
6305 if (is_darwin) sf: {
6306 // Try using sendfile on macOS.
6307 if (@atomicLoad(UseSendfile, &t.use_sendfile, .monotonic) == .disabled) break :sf;
6308 const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf;
6309 var hdtr_data: std.c.sf_hdtr = undefined;
6310 var headers: [2]posix.iovec_const = undefined;
6311 var headers_i: u8 = 0;
6312 if (header.len != 0) {
6313 headers[headers_i] = .{ .base = header.ptr, .len = header.len };
6314 headers_i += 1;
6315 }
6316 if (reader_buffered.len != 0) {
6317 headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len };
6318 headers_i += 1;
6319 }
6320 const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: {
6321 hdtr_data = .{
6322 .headers = &headers,
6323 .hdr_cnt = headers_i,
6324 .trailers = null,
6325 .trl_cnt = 0,
6326 };
6327 break :b &hdtr_data;
6328 };
6329 const max_count = std.math.maxInt(i32); // Avoid EINVAL.
6330 var len: std.c.off_t = @min(file_limit, max_count);
6331 const flags = 0;
6332 const current_thread = Thread.getCurrent(t);
6333 try current_thread.beginSyscall();
6334 while (true) {
6335 switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, &len, hdtr, flags))) {
6336 .SUCCESS => {
6337 current_thread.endSyscall();
6338 break;
6339 },
6340 .OPNOTSUPP, .NOTSOCK, .NOSYS => {
6341 // Give calling code chance to observe before trying
6342 // something else.
6343 current_thread.endSyscall();
6344 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic);
6345 return 0;
6346 },
6347 .INTR => {
6348 if (len == 0) {
6349 try current_thread.checkCancel();
6350 continue;
6351 } else {
6352 // Even if we are being canceled, there have been side
6353 // effects, so it is better to report those side
6354 // effects to the caller.
6355 current_thread.endSyscall();
6356 break;
6357 }
6358 },
6359 .AGAIN => {
6360 current_thread.endSyscall();
6361 if (len == 0) return error.WouldBlock;
6362 break;
6363 },
6364 else => |e| {
6365 current_thread.endSyscall();
6366 assert(error.Unexpected == switch (e) {
6367 .NOTCONN => return error.BrokenPipe,
6368 .IO => return error.InputOutput,
6369 .PIPE => return error.BrokenPipe,
6370 .BADF => |err| errnoBug(err),
6371 .FAULT => |err| errnoBug(err),
6372 .INVAL => |err| errnoBug(err),
6373 else => |err| posix.unexpectedErrno(err),
6374 });
6375 // Give calling code chance to observe the error before trying
6376 // something else.
6377 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic);
6378 return 0;
6379 },
6380 }
6381 }
6382 if (len == 0) {
6383 file_reader.size = file_reader.pos;
6384 return error.EndOfStream;
6385 }
6386 const u_len: usize = @bitCast(len);
6387 file_reader.seekBy(u_len -| header.len) catch return error.ReadFailed;
6388 return u_len;
6389 }
6390
6391 if (native_os == .linux) sf: {
6392 // Try using sendfile on Linux.
6393 if (@atomicLoad(UseSendfile, &t.use_sendfile, .monotonic) == .disabled) break :sf;
6394 // Linux sendfile does not support headers.
6395 if (header.len != 0 or reader_buffered.len != 0) {
6396 const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1);
6397 file_reader.seekBy(n -| header.len) catch return error.ReadFailed;
6398 return n;
6399 }
6400 const max_count = 0x7ffff000; // Avoid EINVAL.
6401 var off: std.os.linux.off_t = undefined;
6402 const off_ptr: ?*std.os.linux.off_t, const count: usize = switch (file_reader.mode) {
6403 .positional => o: {
6404 const size = file_reader.getSize() catch return 0;
6405 off = std.math.cast(std.os.linux.off_t, file_reader.pos) orelse return error.ReadFailed;
6406 break :o .{ &off, @min(@intFromEnum(limit), size - file_reader.pos, max_count) };
6407 },
6408 .streaming => .{ null, limit.minInt(max_count) },
6409 .streaming_reading, .positional_reading => break :sf,
6410 .failure => return error.ReadFailed,
6411 };
6412 const current_thread = Thread.getCurrent(t);
6413 try current_thread.beginSyscall();
6414 const n: usize = while (true) {
6415 const rc = sendfile_sym(out_fd, in_fd, off_ptr, count);
6416 switch (posix.errno(rc)) {
6417 .SUCCESS => {
6418 current_thread.endSyscall();
6419 break @intCast(rc);
6420 },
6421 .NOSYS, .INVAL => {
6422 // Give calling code chance to observe before trying
6423 // something else.
6424 current_thread.endSyscall();
6425 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic);
6426 return 0;
6427 },
6428 .INTR => {
6429 try current_thread.checkCancel();
6430 continue;
6431 },
6432 .CANCELED => return current_thread.endSyscallCanceled(),
6433 else => |e| {
6434 current_thread.endSyscall();
6435 assert(error.Unexpected == switch (e) {
6436 .NOTCONN => return error.BrokenPipe, // `out_fd` is an unconnected socket
6437 .AGAIN => return error.WouldBlock,
6438 .IO => return error.InputOutput,
6439 .PIPE => return error.BrokenPipe,
6440 .NOMEM => return error.SystemResources,
6441 .NXIO, .SPIPE => {
6442 file_reader.mode = file_reader.mode.toStreaming();
6443 const pos = file_reader.pos;
6444 if (pos != 0) {
6445 file_reader.pos = 0;
6446 file_reader.seekBy(@intCast(pos)) catch {
6447 file_reader.mode = .failure;
6448 return error.ReadFailed;
6449 };
6450 }
6451 return 0;
6452 },
6453 .BADF => |err| errnoBug(err), // Always a race condition.
6454 .FAULT => |err| errnoBug(err), // Segmentation fault.
6455 .OVERFLOW => |err| errnoBug(err), // We avoid passing too large of a `count`.
6456 else => |err| posix.unexpectedErrno(err),
6457 });
6458 // Give calling code chance to observe the error before trying
6459 // something else.
6460 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic);
6461 return 0;
6462 },
6463 }
6464 };
6465 if (n == 0) {
6466 file_reader.size = file_reader.pos;
6467 return error.EndOfStream;
6468 }
6469 file_reader.pos += n;
6470 return n;
6471 }
6472
6473 if (have_copy_file_range) cfr: {
6474 if (@atomicLoad(UseCopyFileRange, &t.use_copy_file_range, .monotonic) == .disabled) break :cfr;
6475 if (header.len != 0 or reader_buffered.len != 0) {
6476 const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1);
6477 file_reader.seekBy(n -| header.len) catch return error.ReadFailed;
6478 return n;
6479 }
6480 var off_in: i64 = undefined;
6481 const off_in_ptr: ?*i64 = switch (file_reader.mode) {
6482 .positional_reading, .streaming_reading => return error.Unimplemented,
6483 .positional => p: {
6484 off_in = @intCast(file_reader.pos);
6485 break :p &off_in;
6486 },
6487 .streaming => null,
6488 .failure => return error.WriteFailed,
6489 };
6490 const current_thread = Thread.getCurrent(t);
6491 const n: usize = switch (native_os) {
6492 .linux => n: {
6493 try current_thread.beginSyscall();
6494 while (true) {
6495 const rc = linux_copy_file_range_sys.copy_file_range(in_fd, off_in_ptr, out_fd, null, @intFromEnum(limit), 0);
6496 switch (linux_copy_file_range_sys.errno(rc)) {
6497 .SUCCESS => {
6498 current_thread.endSyscall();
6499 break :n @intCast(rc);
6500 },
6501 .INTR => {
6502 try current_thread.checkCancel();
6503 continue;
6504 },
6505 .CANCELED => return current_thread.endSyscallCanceled(),
6506 .OPNOTSUPP, .INVAL, .NOSYS => {
6507 // Give calling code chance to observe before trying
6508 // something else.
6509 current_thread.endSyscall();
6510 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
6511 return 0;
6512 },
6513 else => |e| {
6514 current_thread.endSyscall();
6515 assert(error.Unexpected == switch (e) {
6516 .FBIG => return error.FileTooBig,
6517 .IO => return error.InputOutput,
6518 .ISDIR => return error.IsDir,
6519 .NOMEM => return error.SystemResources,
6520 .NOSPC => return error.NoSpaceLeft,
6521 .OVERFLOW => return error.Overflow,
6522 .PERM => return error.PermissionDenied,
6523 .TXTBSY => return error.SwapFile,
6524 .XDEV => return error.NotSameFileSystem,
6525 .BADF => |err| errnoBug(err),
6526 else => |err| posix.unexpectedErrno(err),
6527 });
6528 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
6529 return 0;
6530 },
6531 }
6532 }
6533 },
6534 .freebsd => n: {
6535 try current_thread.beginSyscall();
6536 while (true) {
6537 const rc = std.c.copy_file_range(in_fd, off_in_ptr, out_fd, null, @intFromEnum(limit), 0);
6538 switch (std.c.errno(rc)) {
6539 .SUCCESS => {
6540 current_thread.endSyscall();
6541 break :n @intCast(rc);
6542 },
6543 .INTR => {
6544 try current_thread.checkCancel();
6545 continue;
6546 },
6547 .OPNOTSUPP, .INVAL, .NOSYS => {
6548 // Give calling code chance to observe before trying
6549 // something else.
6550 current_thread.endSyscall();
6551 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
6552 return 0;
6553 },
6554 else => |e| {
6555 current_thread.endSyscall();
6556 assert(error.Unexpected == switch (e) {
6557 .FBIG => return error.FileTooBig,
6558 .IO => return error.InputOutput,
6559 .INTEGRITY => return error.CorruptedData,
6560 .ISDIR => return error.IsDir,
6561 .NOSPC => return error.NoSpaceLeft,
6562 .BADF => |err| errnoBug(err),
6563 else => |err| posix.unexpectedErrno(err),
6564 });
6565 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
6566 return 0;
6567 },
6568 }
6569 }
6570 },
6571 else => comptime unreachable,
6572 };
6573 if (n == 0) {
6574 file_reader.size = file_reader.pos;
6575 return error.EndOfStream;
6576 }
6577 file_reader.pos += n;
6578 return n;
6579 }
6580
6581 return error.Unimplemented;
6582}
6583
6584fn fileWriteFilePositional(
6585 userdata: ?*anyopaque,
6586 file: Io.File,
6587 header: []const u8,
6588 file_reader: *Io.File.Reader,
6589 limit: Io.Limit,
6590 offset: u64,
6591) Io.File.WriteFilePositionalError!usize {
6592 const t: *Threaded = @ptrCast(@alignCast(userdata));
6593 const reader_buffered = file_reader.interface.buffered();
6594 if (reader_buffered.len >= @intFromEnum(limit)) {
6595 const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset);
6596 file_reader.seekBy(n -| header.len) catch return error.ReadFailed;
6597 return n;
6598 }
6599 const out_fd = file.handle;
6600 const in_fd = file_reader.file.handle;
6601
6602 if (file_reader.size) |size| {
6603 if (size - file_reader.pos == 0) {
6604 if (reader_buffered.len != 0) {
6605 const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset);
6606 file_reader.seekBy(n -| header.len) catch return error.ReadFailed;
6607 return n;
6608 } else {
6609 return error.EndOfStream;
6610 }
6611 }
6612 }
6613
6614 if (have_copy_file_range) cfr: {
6615 if (@atomicLoad(UseCopyFileRange, &t.use_copy_file_range, .monotonic) == .disabled) break :cfr;
6616 if (header.len != 0 or reader_buffered.len != 0) {
6617 const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset);
6618 file_reader.seekBy(n -| header.len) catch return error.ReadFailed;
6619 return n;
6620 }
6621 var off_in: i64 = undefined;
6622 const off_in_ptr: ?*i64 = switch (file_reader.mode) {
6623 .positional_reading, .streaming_reading => return error.Unimplemented,
6624 .positional => p: {
6625 off_in = @intCast(file_reader.pos);
6626 break :p &off_in;
6627 },
6628 .streaming => null,
6629 .failure => return error.WriteFailed,
6630 };
6631 var off_out: i64 = @intCast(offset);
6632 const current_thread = Thread.getCurrent(t);
6633 const n: usize = switch (native_os) {
6634 .linux => n: {
6635 try current_thread.beginSyscall();
6636 while (true) {
6637 const rc = linux_copy_file_range_sys.copy_file_range(in_fd, off_in_ptr, out_fd, &off_out, @intFromEnum(limit), 0);
6638 switch (linux_copy_file_range_sys.errno(rc)) {
6639 .SUCCESS => {
6640 current_thread.endSyscall();
6641 break :n @intCast(rc);
6642 },
6643 .INTR => {
6644 try current_thread.checkCancel();
6645 continue;
6646 },
6647 .CANCELED => return current_thread.endSyscallCanceled(),
6648 .OPNOTSUPP, .INVAL, .NOSYS => {
6649 // Give calling code chance to observe before trying
6650 // something else.
6651 current_thread.endSyscall();
6652 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
6653 return 0;
6654 },
6655 else => |e| {
6656 current_thread.endSyscall();
6657 assert(error.Unexpected == switch (e) {
6658 .FBIG => return error.FileTooBig,
6659 .IO => return error.InputOutput,
6660 .ISDIR => return error.IsDir,
6661 .NOMEM => return error.SystemResources,
6662 .NOSPC => return error.NoSpaceLeft,
6663 .OVERFLOW => return error.Unseekable,
6664 .NXIO => return error.Unseekable,
6665 .SPIPE => return error.Unseekable,
6666 .PERM => return error.PermissionDenied,
6667 .TXTBSY => return error.SwapFile,
6668 .XDEV => return error.NotSameFileSystem,
6669 .BADF => |err| errnoBug(err),
6670 else => |err| posix.unexpectedErrno(err),
6671 });
6672 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
6673 return 0;
6674 },
6675 }
6676 }
6677 },
6678 .freebsd => n: {
6679 try current_thread.beginSyscall();
6680 while (true) {
6681 const rc = std.c.copy_file_range(in_fd, off_in_ptr, out_fd, &off_out, @intFromEnum(limit), 0);
6682 switch (std.c.errno(rc)) {
6683 .SUCCESS => {
6684 current_thread.endSyscall();
6685 break :n @intCast(rc);
6686 },
6687 .INTR => {
6688 try current_thread.checkCancel();
6689 continue;
6690 },
6691 .OPNOTSUPP, .INVAL, .NOSYS => {
6692 // Give calling code chance to observe before trying
6693 // something else.
6694 current_thread.endSyscall();
6695 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
6696 return 0;
6697 },
6698 else => |e| {
6699 current_thread.endSyscall();
6700 assert(error.Unexpected == switch (e) {
6701 .FBIG => return error.FileTooBig,
6702 .IO => return error.InputOutput,
6703 .INTEGRITY => return error.CorruptedData,
6704 .ISDIR => return error.IsDir,
6705 .NOSPC => return error.NoSpaceLeft,
6706 .OVERFLOW => return error.Unseekable,
6707 .NXIO => return error.Unseekable,
6708 .SPIPE => return error.Unseekable,
6709 .BADF => |err| errnoBug(err),
6710 else => |err| posix.unexpectedErrno(err),
6711 });
6712 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
6713 return 0;
6714 },
6715 }
6716 }
6717 },
6718 else => comptime unreachable,
6719 };
6720 if (n == 0) {
6721 file_reader.size = file_reader.pos;
6722 return error.EndOfStream;
6723 }
6724 file_reader.pos += n;
6725 return n;
6726 }
6727
6728 if (is_darwin) fcf: {
6729 if (@atomicLoad(UseFcopyfile, &t.use_fcopyfile, .monotonic) == .disabled) break :fcf;
6730 if (file_reader.pos != 0) break :fcf;
6731 if (offset != 0) break :fcf;
6732 if (limit != .unlimited) break :fcf;
6733 const size = file_reader.getSize() catch break :fcf;
6734 if (header.len != 0 or reader_buffered.len != 0) {
6735 const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset);
6736 file_reader.seekBy(n -| header.len) catch return error.ReadFailed;
6737 return n;
6738 }
6739 const current_thread = Thread.getCurrent(t);
6740 try current_thread.beginSyscall();
6741 while (true) {
6742 const rc = std.c.fcopyfile(in_fd, out_fd, null, .{ .DATA = true });
6743 switch (posix.errno(rc)) {
6744 .SUCCESS => {
6745 current_thread.endSyscall();
6746 break;
6747 },
6748 .INTR => {
6749 try current_thread.checkCancel();
6750 continue;
6751 },
6752 .OPNOTSUPP => {
6753 // Give calling code chance to observe before trying
6754 // something else.
6755 current_thread.endSyscall();
6756 @atomicStore(UseFcopyfile, &t.use_fcopyfile, .disabled, .monotonic);
6757 return 0;
6758 },
6759 else => |e| {
6760 current_thread.endSyscall();
6761 assert(error.Unexpected == switch (e) {
6762 .NOMEM => return error.SystemResources,
6763 .INVAL => |err| posix.errnoBug(err),
6764 else => |err| posix.unexpectedErrno(err),
6765 });
6766 return 0;
6767 },
6768 }
6769 }
6770 file_reader.pos = size;
6771 return size;
59196772 }
6773
6774 return error.Unimplemented;
59206775}
59216776
59226777fn nowPosix(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
......@@ -7847,6 +8702,7 @@ fn netWritePosix(
78478702 },
78488703 };
78498704 const flags = posix.MSG.NOSIGNAL;
8705
78508706 try current_thread.beginSyscall();
78518707 while (true) {
78528708 const rc = posix.system.sendmsg(fd, &msg, flags);
......@@ -8010,7 +8866,11 @@ fn netWriteUnavailable(
80108866 return error.NetworkDown;
80118867}
80128868
8013fn addBuf(v: []posix.iovec_const, i: *@FieldType(posix.msghdr_const, "iovlen"), bytes: []const u8) void {
8869/// This is either usize or u32. Since, either is fine, let's use the same
8870/// `addBuf` function for both writing to a file and sending network messages.
8871const iovlen_t = @FieldType(posix.msghdr_const, "iovlen");
8872
8873fn addBuf(v: []posix.iovec_const, i: *iovlen_t, bytes: []const u8) void {
80148874 // OS checks ptr addr before length so zero length vectors must be omitted.
80158875 if (bytes.len == 0) return;
80168876 if (v.len - i.* == 0) return;
......@@ -8336,7 +9196,7 @@ fn netLookupFallible(
83369196 // TODO use dnsres_getaddrinfo
83379197 }
83389198
8339 if (native_os.isDarwin()) {
9199 if (is_darwin) {
83409200 // TODO use CFHostStartInfoResolution / CFHostCancelInfoResolution
83419201 }
83429202
lib/std/os.zig-1
......@@ -30,7 +30,6 @@ pub const uefi = @import("os/uefi.zig");
3030pub const wasi = @import("os/wasi.zig");
3131pub const emscripten = @import("os/emscripten.zig");
3232pub const windows = @import("os/windows.zig");
33pub const freebsd = @import("os/freebsd.zig");
3433
3534test {
3635 _ = linux;
lib/std/os/freebsd.zig deleted-50
......@@ -1,50 +0,0 @@
1const std = @import("../std.zig");
2const fd_t = std.c.fd_t;
3const off_t = std.c.off_t;
4const unexpectedErrno = std.posix.unexpectedErrno;
5const errno = std.posix.errno;
6const builtin = @import("builtin");
7
8pub const CopyFileRangeError = std.posix.UnexpectedError || error{
9 /// If infd is not open for reading or outfd is not open for writing, or
10 /// opened for writing with O_APPEND, or if infd and outfd refer to the
11 /// same file.
12 BadFileFlags,
13 /// If the copy exceeds the process's file size limit or the maximum
14 /// file size for the file system outfd re- sides on.
15 FileTooBig,
16 /// A signal interrupted the system call before it could be completed.
17 /// This may happen for files on some NFS mounts. When this happens,
18 /// the values pointed to by inoffp and outoffp are reset to the
19 /// initial values for the system call.
20 Interrupted,
21 /// One of:
22 /// * infd and outfd refer to the same file and the byte ranges overlap.
23 /// * The flags argument is not zero.
24 /// * Either infd or outfd refers to a file object that is not a regular file.
25 InvalidArguments,
26 /// An I/O error occurred while reading/writing the files.
27 InputOutput,
28 /// Corrupted data was detected while reading from a file system.
29 CorruptedData,
30 /// Either infd or outfd refers to a directory.
31 IsDir,
32 /// File system that stores outfd is full.
33 NoSpaceLeft,
34};
35
36pub fn copy_file_range(fd_in: fd_t, off_in: ?*i64, fd_out: fd_t, off_out: ?*i64, len: usize, flags: u32) CopyFileRangeError!usize {
37 const rc = std.c.copy_file_range(fd_in, off_in, fd_out, off_out, len, flags);
38 switch (errno(rc)) {
39 .SUCCESS => return @intCast(rc),
40 .BADF => return error.BadFileFlags,
41 .FBIG => return error.FileTooBig,
42 .INTR => return error.Interrupted,
43 .INVAL => return error.InvalidArguments,
44 .IO => return error.InputOutput,
45 .INTEGRITY => return error.CorruptedData,
46 .ISDIR => return error.IsDir,
47 .NOSPC => return error.NoSpaceLeft,
48 else => |err| return unexpectedErrno(err),
49 }
50}
lib/std/os/linux.zig-162
......@@ -9872,165 +9872,3 @@ pub const cmsghdr = extern struct {
98729872 level: i32,
98739873 type: i32,
98749874};
9875
9876/// The syscalls, but with Zig error sets, going through libc if linking libc,
9877/// and with some footguns eliminated.
9878pub const wrapped = struct {
9879 pub const lfs64_abi = builtin.link_libc and (builtin.abi.isGnu() or builtin.abi.isAndroid());
9880 const system = if (builtin.link_libc) std.c else std.os.linux;
9881
9882 pub const SendfileError = std.posix.UnexpectedError || error{
9883 /// `out_fd` is an unconnected socket, or out_fd closed its read end.
9884 BrokenPipe,
9885 /// Descriptor is not valid or locked, or an mmap(2)-like operation is not available for in_fd.
9886 UnsupportedOperation,
9887 /// Nonblocking I/O has been selected but the write would block.
9888 WouldBlock,
9889 /// Unspecified error while reading from in_fd.
9890 InputOutput,
9891 /// Insufficient kernel memory to read from in_fd.
9892 SystemResources,
9893 /// `offset` is not `null` but the input file is not seekable.
9894 Unseekable,
9895 };
9896
9897 pub fn sendfile(
9898 out_fd: fd_t,
9899 in_fd: fd_t,
9900 in_offset: ?*off_t,
9901 in_len: usize,
9902 ) SendfileError!usize {
9903 const adjusted_len = @min(in_len, 0x7ffff000); // Prevents EOVERFLOW.
9904 const sendfileSymbol = if (lfs64_abi) system.sendfile64 else system.sendfile;
9905 const rc = sendfileSymbol(out_fd, in_fd, in_offset, adjusted_len);
9906 switch (system.errno(rc)) {
9907 .SUCCESS => return @intCast(rc),
9908 .BADF => return invalidApiUsage(), // Always a race condition.
9909 .FAULT => return invalidApiUsage(), // Segmentation fault.
9910 .OVERFLOW => return unexpectedErrno(.OVERFLOW), // We avoid passing too large of a `count`.
9911 .NOTCONN => return error.BrokenPipe, // `out_fd` is an unconnected socket
9912 .INVAL => return error.UnsupportedOperation,
9913 .AGAIN => return error.WouldBlock,
9914 .IO => return error.InputOutput,
9915 .PIPE => return error.BrokenPipe,
9916 .NOMEM => return error.SystemResources,
9917 .NXIO => return error.Unseekable,
9918 .SPIPE => return error.Unseekable,
9919 else => |err| return unexpectedErrno(err),
9920 }
9921 }
9922
9923 pub const CopyFileRangeError = std.posix.UnexpectedError || error{
9924 /// One of:
9925 /// * One or more file descriptors are not valid.
9926 /// * fd_in is not open for reading; or fd_out is not open for writing.
9927 /// * The O_APPEND flag is set for the open file description referred
9928 /// to by the file descriptor fd_out.
9929 BadFileFlags,
9930 /// One of:
9931 /// * An attempt was made to write at a position past the maximum file
9932 /// offset the kernel supports.
9933 /// * An attempt was made to write a range that exceeds the allowed
9934 /// maximum file size. The maximum file size differs between
9935 /// filesystem implementations and can be different from the maximum
9936 /// allowed file offset.
9937 /// * An attempt was made to write beyond the process's file size
9938 /// resource limit. This may also result in the process receiving a
9939 /// SIGXFSZ signal.
9940 FileTooBig,
9941 /// One of:
9942 /// * either fd_in or fd_out is not a regular file
9943 /// * flags argument is not zero
9944 /// * fd_in and fd_out refer to the same file and the source and target ranges overlap.
9945 InvalidArguments,
9946 /// A low-level I/O error occurred while copying.
9947 InputOutput,
9948 /// Either fd_in or fd_out refers to a directory.
9949 IsDir,
9950 OutOfMemory,
9951 /// There is not enough space on the target filesystem to complete the copy.
9952 NoSpaceLeft,
9953 /// (since Linux 5.19) the filesystem does not support this operation.
9954 OperationNotSupported,
9955 /// The requested source or destination range is too large to represent
9956 /// in the specified data types.
9957 Overflow,
9958 /// fd_out refers to an immutable file.
9959 PermissionDenied,
9960 /// Either fd_in or fd_out refers to an active swap file.
9961 SwapFile,
9962 /// The files referred to by fd_in and fd_out are not on the same
9963 /// filesystem, and the source and target filesystems are not of the
9964 /// same type, or do not support cross-filesystem copy.
9965 NotSameFileSystem,
9966 };
9967
9968 pub fn copy_file_range(fd_in: fd_t, off_in: ?*i64, fd_out: fd_t, off_out: ?*i64, len: usize, flags: u32) CopyFileRangeError!usize {
9969 const use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) .{ .major = 34, .minor = 0, .patch = 0 } else .{ .major = 2, .minor = 27, .patch = 0 });
9970 const sys = if (use_c) std.c else std.os.linux;
9971 const rc = sys.copy_file_range(fd_in, off_in, fd_out, off_out, len, flags);
9972 switch (sys.errno(rc)) {
9973 .SUCCESS => return @intCast(rc),
9974 .BADF => return error.BadFileFlags,
9975 .FBIG => return error.FileTooBig,
9976 .INVAL => return error.InvalidArguments,
9977 .IO => return error.InputOutput,
9978 .ISDIR => return error.IsDir,
9979 .NOMEM => return error.OutOfMemory,
9980 .NOSPC => return error.NoSpaceLeft,
9981 .OPNOTSUPP => return error.OperationNotSupported,
9982 .OVERFLOW => return error.Overflow,
9983 .PERM => return error.PermissionDenied,
9984 .TXTBSY => return error.SwapFile,
9985 .XDEV => return error.NotSameFileSystem,
9986 else => |err| return unexpectedErrno(err),
9987 }
9988 }
9989
9990 pub const StatxError = std.posix.UnexpectedError || error{
9991 /// Search permission is denied for one of the directories in `path`.
9992 AccessDenied,
9993 /// Too many symbolic links were encountered traversing `path`.
9994 SymLinkLoop,
9995 /// `path` is too long.
9996 NameTooLong,
9997 /// One of:
9998 /// - A component of `path` does not exist.
9999 /// - A component of `path` is not a directory.
10000 /// - `path` is a relative and `dirfd` is not a directory file descriptor.
10001 FileNotFound,
10002 /// Insufficient memory is available.
10003 SystemResources,
10004 };
10005
10006 pub fn statx(dirfd: fd_t, path: [*:0]const u8, flags: u32, mask: STATX) StatxError!Statx {
10007 const use_c = std.c.versionCheck(if (builtin.abi.isAndroid())
10008 .{ .major = 30, .minor = 0, .patch = 0 }
10009 else
10010 .{ .major = 2, .minor = 28, .patch = 0 });
10011 const sys = if (use_c) std.c else std.os.linux;
10012
10013 var stx = std.mem.zeroes(Statx);
10014 const rc = sys.statx(dirfd, path, flags, mask, &stx);
10015 return switch (sys.errno(rc)) {
10016 .SUCCESS => stx,
10017 .ACCES => error.AccessDenied,
10018 .BADF => invalidApiUsage(),
10019 .FAULT => invalidApiUsage(),
10020 .INVAL => invalidApiUsage(),
10021 .LOOP => error.SymLinkLoop,
10022 .NAMETOOLONG => error.NameTooLong,
10023 .NOENT => error.FileNotFound,
10024 .NOTDIR => error.FileNotFound,
10025 .NOMEM => error.SystemResources,
10026 else => |err| unexpectedErrno(err),
10027 };
10028 }
10029
10030 const unexpectedErrno = std.posix.unexpectedErrno;
10031
10032 fn invalidApiUsage() error{Unexpected} {
10033 if (builtin.mode == .Debug) @panic("invalid API usage");
10034 return error.Unexpected;
10035 }
10036};
lib/std/os/linux/test.zig-8
......@@ -401,14 +401,6 @@ test "futex2_requeue" {
401401 try expectEqual(0, rc);
402402}
403403
404test "copy_file_range error" {
405 const fds = try std.posix.pipe();
406 defer std.posix.close(fds[0]);
407 defer std.posix.close(fds[1]);
408
409 try std.testing.expectError(error.InvalidArguments, linux.wrapped.copy_file_range(fds[0], null, fds[1], null, 1, 0));
410}
411
412404test {
413405 _ = linux.IoUring;
414406}
lib/std/posix.zig-166
......@@ -1157,80 +1157,6 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {
11571157 }
11581158}
11591159
1160/// Write multiple buffers to a file descriptor.
1161/// Retries when interrupted by a signal.
1162/// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero.
1163///
1164/// Note that a successful write() may transfer fewer bytes than supplied. Such partial writes can
1165/// occur for various reasons; for example, because there was insufficient space on the disk
1166/// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or
1167/// similar was interrupted by a signal handler after it had transferred some, but before it had
1168/// transferred all of the requested bytes. In the event of a partial write, the caller can make
1169/// another write() call to transfer the remaining bytes. The subsequent call will either
1170/// transfer further bytes or may result in an error (e.g., if the disk is now full).
1171///
1172/// For POSIX systems, if `fd` is opened in non blocking mode, the function will
1173/// return error.WouldBlock when EAGAIN is received.
1174/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
1175/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
1176///
1177/// If `iov.len` is larger than `IOV_MAX`, a partial write will occur.
1178///
1179/// This function assumes that all vectors, including zero-length vectors, have
1180/// a pointer within the address space of the application.
1181pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize {
1182 if (native_os == .windows) {
1183 // TODO improve this to use WriteFileScatter
1184 if (iov.len == 0) return 0;
1185 const first = iov[0];
1186 return write(fd, first.base[0..first.len]);
1187 }
1188 if (native_os == .wasi and !builtin.link_libc) {
1189 var nwritten: usize = undefined;
1190 switch (wasi.fd_write(fd, iov.ptr, iov.len, &nwritten)) {
1191 .SUCCESS => return nwritten,
1192 .INTR => unreachable,
1193 .INVAL => unreachable,
1194 .FAULT => unreachable,
1195 .AGAIN => unreachable,
1196 .BADF => return error.NotOpenForWriting, // can be a race condition.
1197 .DESTADDRREQ => unreachable, // `connect` was never called.
1198 .DQUOT => return error.DiskQuota,
1199 .FBIG => return error.FileTooBig,
1200 .IO => return error.InputOutput,
1201 .NOSPC => return error.NoSpaceLeft,
1202 .PERM => return error.PermissionDenied,
1203 .PIPE => return error.BrokenPipe,
1204 .NOTCAPABLE => return error.AccessDenied,
1205 else => |err| return unexpectedErrno(err),
1206 }
1207 }
1208
1209 while (true) {
1210 const rc = system.writev(fd, iov.ptr, @min(iov.len, IOV_MAX));
1211 switch (errno(rc)) {
1212 .SUCCESS => return @intCast(rc),
1213 .INTR => continue,
1214 .INVAL => return error.InvalidArgument,
1215 .FAULT => unreachable,
1216 .SRCH => return error.ProcessNotFound,
1217 .AGAIN => return error.WouldBlock,
1218 .BADF => return error.NotOpenForWriting, // Can be a race condition.
1219 .DESTADDRREQ => unreachable, // `connect` was never called.
1220 .DQUOT => return error.DiskQuota,
1221 .FBIG => return error.FileTooBig,
1222 .IO => return error.InputOutput,
1223 .NOSPC => return error.NoSpaceLeft,
1224 .PERM => return error.PermissionDenied,
1225 .PIPE => return error.BrokenPipe,
1226 .CONNRESET => return error.ConnectionResetByPeer,
1227 .BUSY => return error.DeviceBusy,
1228 .CANCELED => return error.Canceled,
1229 else => |err| return unexpectedErrno(err),
1230 }
1231 }
1232}
1233
12341160pub const PWriteError = WriteError || error{Unseekable};
12351161
12361162/// Write to a file descriptor, with a position offset.
......@@ -4384,98 +4310,6 @@ pub fn send(
43844310 };
43854311}
43864312
4387pub const CopyFileRangeError = error{
4388 FileTooBig,
4389 InputOutput,
4390 /// `fd_in` is not open for reading; or `fd_out` is not open for writing;
4391 /// or the `APPEND` flag is set for `fd_out`.
4392 FilesOpenedWithWrongFlags,
4393 IsDir,
4394 OutOfMemory,
4395 NoSpaceLeft,
4396 Unseekable,
4397 PermissionDenied,
4398 SwapFile,
4399 CorruptedData,
4400} || PReadError || PWriteError || UnexpectedError;
4401
4402/// Transfer data between file descriptors at specified offsets.
4403///
4404/// Returns the number of bytes written, which can less than requested.
4405///
4406/// The `copy_file_range` call copies `len` bytes from one file descriptor to another. When possible,
4407/// this is done within the operating system kernel, which can provide better performance
4408/// characteristics than transferring data from kernel to user space and back, such as with
4409/// `pread` and `pwrite` calls.
4410///
4411/// `fd_in` must be a file descriptor opened for reading, and `fd_out` must be a file descriptor
4412/// opened for writing. They may be any kind of file descriptor; however, if `fd_in` is not a regular
4413/// file system file, it may cause this function to fall back to calling `pread` and `pwrite`, in which case
4414/// atomicity guarantees no longer apply.
4415///
4416/// If `fd_in` and `fd_out` are the same, source and target ranges must not overlap.
4417/// The file descriptor seek positions are ignored and not updated.
4418/// When `off_in` is past the end of the input file, it successfully reads 0 bytes.
4419///
4420/// `flags` has different meanings per operating system; refer to the respective man pages.
4421///
4422/// These systems support in-kernel data copying:
4423/// * Linux (cross-filesystem from version 5.3)
4424/// * FreeBSD 13.0
4425///
4426/// Other systems fall back to calling `pread` / `pwrite`.
4427///
4428/// Maximum offsets on Linux and FreeBSD are `maxInt(i64)`.
4429pub fn copy_file_range(fd_in: fd_t, off_in: u64, fd_out: fd_t, off_out: u64, len: usize, flags: u32) CopyFileRangeError!usize {
4430 if (builtin.os.tag == .freebsd or builtin.os.tag == .linux) {
4431 const use_c = native_os != .linux or
4432 std.c.versionCheck(if (builtin.abi.isAndroid()) .{ .major = 34, .minor = 0, .patch = 0 } else .{ .major = 2, .minor = 27, .patch = 0 });
4433 const sys = if (use_c) std.c else linux;
4434
4435 var off_in_copy: i64 = @bitCast(off_in);
4436 var off_out_copy: i64 = @bitCast(off_out);
4437
4438 while (true) {
4439 const rc = sys.copy_file_range(fd_in, &off_in_copy, fd_out, &off_out_copy, len, flags);
4440 if (native_os == .freebsd) {
4441 switch (sys.errno(rc)) {
4442 .SUCCESS => return @intCast(rc),
4443 .BADF => return error.FilesOpenedWithWrongFlags,
4444 .FBIG => return error.FileTooBig,
4445 .IO => return error.InputOutput,
4446 .ISDIR => return error.IsDir,
4447 .NOSPC => return error.NoSpaceLeft,
4448 .INVAL => break, // these may not be regular files, try fallback
4449 .INTEGRITY => return error.CorruptedData,
4450 .INTR => continue,
4451 else => |err| return unexpectedErrno(err),
4452 }
4453 } else { // assume linux
4454 switch (sys.errno(rc)) {
4455 .SUCCESS => return @intCast(rc),
4456 .BADF => return error.FilesOpenedWithWrongFlags,
4457 .FBIG => return error.FileTooBig,
4458 .IO => return error.InputOutput,
4459 .ISDIR => return error.IsDir,
4460 .NOSPC => return error.NoSpaceLeft,
4461 .INVAL => break, // these may not be regular files, try fallback
4462 .NOMEM => return error.OutOfMemory,
4463 .OVERFLOW => return error.Unseekable,
4464 .PERM => return error.PermissionDenied,
4465 .TXTBSY => return error.SwapFile,
4466 .XDEV => break, // support for cross-filesystem copy added in Linux 5.3, use fallback
4467 else => |err| return unexpectedErrno(err),
4468 }
4469 }
4470 }
4471 }
4472
4473 var buf: [8 * 4096]u8 = undefined;
4474 const amt_read = try pread(fd_in, buf[0..@min(buf.len, len)], off_in);
4475 if (amt_read == 0) return 0;
4476 return pwrite(fd_out, buf[0..amt_read], off_out);
4477}
4478
44794313pub const PollError = error{
44804314 /// The network subsystem has failed.
44814315 NetworkDown,