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 {...@@ -662,8 +662,8 @@ pub const VTable = struct {
662 futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void,662 futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void,
663 futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void,663 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,665 dirMake: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.MakeError!void,
666 dirMakePath: *const fn (?*anyopaque, Dir, []const u8, Dir.Mode) Dir.MakePathError!Dir.MakePathStatus,666 dirMakePath: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.MakePathError!Dir.MakePathStatus,
667 dirMakeOpenPath: *const fn (?*anyopaque, Dir, []const u8, Dir.OpenOptions) Dir.MakeOpenPathError!Dir,667 dirMakeOpenPath: *const fn (?*anyopaque, Dir, []const u8, Dir.OpenOptions) Dir.MakeOpenPathError!Dir,
668 dirStat: *const fn (?*anyopaque, Dir) Dir.StatError!Dir.Stat,668 dirStat: *const fn (?*anyopaque, Dir) Dir.StatError!Dir.Stat,
669 dirStatPath: *const fn (?*anyopaque, Dir, []const u8, Dir.StatPathOptions) Dir.StatPathError!File.Stat,669 dirStatPath: *const fn (?*anyopaque, Dir, []const u8, Dir.StatPathOptions) Dir.StatPathError!File.Stat,
...@@ -687,8 +687,10 @@ pub const VTable = struct {...@@ -687,8 +687,10 @@ pub const VTable = struct {
687 fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat,687 fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat,
688 fileLength: *const fn (?*anyopaque, File) File.LengthError!u64,688 fileLength: *const fn (?*anyopaque, File) File.LengthError!u64,
689 fileClose: *const fn (?*anyopaque, File) void,689 fileClose: *const fn (?*anyopaque, File) void,
690 fileWriteStreaming: *const fn (?*anyopaque, File, buffer: [][]const u8) File.WriteStreamingError!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, buffer: [][]const u8, offset: u64) File.WritePositionalError!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,
692 /// Returns 0 on end of stream.694 /// Returns 0 on end of stream.
693 fileReadStreaming: *const fn (?*anyopaque, File, data: [][]u8) File.Reader.Error!usize,695 fileReadStreaming: *const fn (?*anyopaque, File, data: [][]u8) File.Reader.Error!usize,
694 /// Returns 0 on end of stream.696 /// Returns 0 on end of stream.
...@@ -724,6 +726,7 @@ pub const VTable = struct {...@@ -724,6 +726,7 @@ pub const VTable = struct {
724 /// Returns 0 on end of stream.726 /// Returns 0 on end of stream.
725 netRead: *const fn (?*anyopaque, src: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize,727 netRead: *const fn (?*anyopaque, src: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize,
726 netWrite: *const fn (?*anyopaque, dest: net.Socket.Handle, header: []const u8, data: []const []const u8, splat: usize) net.Stream.Writer.Error!usize,728 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,
727 netClose: *const fn (?*anyopaque, handle: net.Socket.Handle) void,730 netClose: *const fn (?*anyopaque, handle: net.Socket.Handle) void,
728 netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface,731 netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface,
729 netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name,732 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{...@@ -652,7 +652,6 @@ pub const RealPathError = error{
652 NoSpaceLeft,652 NoSpaceLeft,
653 FileSystem,653 FileSystem,
654 DeviceBusy,654 DeviceBusy,
655 ProcessNotFound,
656 SharingViolation,655 SharingViolation,
657 PipeBusy,656 PipeBusy,
658 /// Windows: file paths provided by the user must be valid WTF-8.657 /// Windows: file paths provided by the user must be valid WTF-8.
...@@ -1038,7 +1037,6 @@ pub const DeleteTreeError = error{...@@ -1038,7 +1037,6 @@ pub const DeleteTreeError = error{
1038 FileSystem,1037 FileSystem,
1039 FileBusy,1038 FileBusy,
1040 DeviceBusy,1039 DeviceBusy,
1041 ProcessNotFound,
1042 /// One of the path components was not a directory.1040 /// One of the path components was not a directory.
1043 /// This error is unreachable if `sub_path` does not contain a path separator.1041 /// This error is unreachable if `sub_path` does not contain a path separator.
1044 NotDir,1042 NotDir,
lib/std/Io/File.zig+16-8
...@@ -198,7 +198,6 @@ pub const OpenError = error{...@@ -198,7 +198,6 @@ pub const OpenError = error{
198 NoDevice,198 NoDevice,
199 /// On Windows, `\\server` or `\\server\share` was not found.199 /// On Windows, `\\server` or `\\server\share` was not found.
200 NetworkNotFound,200 NetworkNotFound,
201 ProcessNotFound,
202 /// On Windows, antivirus software is enabled by default. It can be201 /// On Windows, antivirus software is enabled by default. It can be
203 /// disabled, but Windows Update sometimes ignores the user's preference202 /// disabled, but Windows Update sometimes ignores the user's preference
204 /// and re-enables it. When enabled, antivirus software on Windows203 /// 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...@@ -477,18 +476,25 @@ pub fn readPositional(file: File, io: Io, buffer: [][]u8, offset: u64) ReadPosit
477 return io.vtable.fileReadPositional(io.userdata, file, buffer, offset);476 return io.vtable.fileReadPositional(io.userdata, file, buffer, offset);
478}477}
479478
480pub const WriteStreamingError = error{} || Io.UnexpectedError || Io.Cancelable;479pub const WritePositionalError = Writer.Error || error{Unseekable};
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};
487480
488pub fn writePositional(file: File, io: Io, buffer: [][]const u8, offset: u64) WritePositionalError!usize {481pub fn writePositional(file: File, io: Io, buffer: [][]const u8, offset: u64) WritePositionalError!usize {
489 return io.vtable.fileWritePositional(io.userdata, file, buffer, offset);482 return io.vtable.fileWritePositional(io.userdata, file, buffer, offset);
490}483}
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
492/// Opens a file for reading or writing, without attempting to create a new498/// Opens a file for reading or writing, without attempting to create a new
493/// file, based on an absolute path.499/// file, based on an absolute path.
494///500///
...@@ -511,6 +517,8 @@ pub const SeekError = error{...@@ -511,6 +517,8 @@ pub const SeekError = error{
511 AccessDenied,517 AccessDenied,
512} || Io.Cancelable || Io.UnexpectedError;518} || Io.Cancelable || Io.UnexpectedError;
513519
520pub const WriteFilePositionalError = Writer.WriteFileError || error{Unseekable};
521
514/// Defaults to positional reading; falls back to streaming.522/// Defaults to positional reading; falls back to streaming.
515///523///
516/// Positional is more threadsafe, since the global seek position is not524/// 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;...@@ -17,13 +17,13 @@ const assert = std.debug.assert;
17io: Io,17io: Io,
18file: File,18file: File,
19err: ?Error = null,19err: ?Error = null,
20mode: Reader.Mode = .positional,20mode: Mode = .positional,
21/// Tracks the true seek position in the file. To obtain the logical21/// Tracks the true seek position in the file. To obtain the logical
22/// position, use `logicalPos`.22/// position, use `logicalPos`.
23pos: u64 = 0,23pos: u64 = 0,
24size: ?u64 = null,24size: ?u64 = null,
25size_err: ?SizeError = null,25size_err: ?SizeError = null,
26seek_err: ?Reader.SeekError = null,26seek_err: ?SeekError = null,
27interface: Io.Reader,27interface: Io.Reader,
2828
29pub const Error = error{29pub const Error = error{
...@@ -37,15 +37,12 @@ pub const Error = error{...@@ -37,15 +37,12 @@ pub const Error = error{
37 /// trying to read a directory file descriptor as if it were a file.37 /// trying to read a directory file descriptor as if it were a file.
38 NotOpenForReading,38 NotOpenForReading,
39 SocketUnconnected,39 SocketUnconnected,
40 /// This error occurs when no global event loop is configured,40 /// Non-blocking has been enabled, and reading from the file descriptor
41 /// and reading from the file descriptor would block.41 /// would block.
42 WouldBlock,42 WouldBlock,
43 /// In WASI, this error occurs when the file descriptor does43 /// In WASI, this error occurs when the file descriptor does
44 /// not hold the required rights to read from it.44 /// not hold the required rights to read from it.
45 AccessDenied,45 AccessDenied,
46 /// This error occurs in Linux if the process to be read from
47 /// no longer exists.
48 ProcessNotFound,
49 /// Unable to read file due to lock.46 /// Unable to read file due to lock.
50 LockViolation,47 LockViolation,
51} || Io.Cancelable || Io.UnexpectedError;48} || Io.Cancelable || Io.UnexpectedError;
...@@ -93,9 +90,9 @@ pub const Mode = enum {...@@ -93,9 +90,9 @@ pub const Mode = enum {
93pub fn initInterface(buffer: []u8) Io.Reader {90pub fn initInterface(buffer: []u8) Io.Reader {
94 return .{91 return .{
95 .vtable = &.{92 .vtable = &.{
96 .stream = Reader.stream,93 .stream = stream,
97 .discard = Reader.discard,94 .discard = discard,
98 .readVec = Reader.readVec,95 .readVec = readVec,
99 },96 },
100 .buffer = buffer,97 .buffer = buffer,
101 .seek = 0,98 .seek = 0,
...@@ -153,7 +150,7 @@ pub fn getSize(r: *Reader) SizeError!u64 {...@@ -153,7 +150,7 @@ pub fn getSize(r: *Reader) SizeError!u64 {
153 };150 };
154}151}
155152
156pub fn seekBy(r: *Reader, offset: i64) Reader.SeekError!void {153pub fn seekBy(r: *Reader, offset: i64) SeekError!void {
157 const io = r.io;154 const io = r.io;
158 switch (r.mode) {155 switch (r.mode) {
159 .positional, .positional_reading => {156 .positional, .positional_reading => {
...@@ -183,7 +180,7 @@ pub fn seekBy(r: *Reader, offset: i64) Reader.SeekError!void {...@@ -183,7 +180,7 @@ pub fn seekBy(r: *Reader, offset: i64) Reader.SeekError!void {
183}180}
184181
185/// Repositions logical read offset relative to the beginning of the file.182/// 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 {
187 const io = r.io;184 const io = r.io;
188 switch (r.mode) {185 switch (r.mode) {
189 .positional, .positional_reading => {186 .positional, .positional_reading => {
...@@ -191,7 +188,7 @@ pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void {...@@ -191,7 +188,7 @@ pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void {
191 },188 },
192 .streaming, .streaming_reading => {189 .streaming, .streaming_reading => {
193 const logical_pos = logicalPos(r);190 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));
195 if (r.seek_err) |err| return err;192 if (r.seek_err) |err| return err;
196 io.vtable.fileSeekTo(io.userdata, r.file, offset) catch |err| {193 io.vtable.fileSeekTo(io.userdata, r.file, offset) catch |err| {
197 r.seek_err = err;194 r.seek_err = err;
...@@ -224,7 +221,7 @@ fn stream(io_reader: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.Strea...@@ -224,7 +221,7 @@ fn stream(io_reader: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.Strea
224 return streamMode(r, w, limit, r.mode);221 return streamMode(r, w, limit, r.mode);
225}222}
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 {
228 switch (mode) {225 switch (mode) {
229 .positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) {226 .positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) {
230 error.Unimplemented => {227 error.Unimplemented => {
lib/std/Io/File/Writer.zig+100-438
...@@ -1,53 +1,38 @@...@@ -1,53 +1,38 @@
1const Writer = @This();1const Writer = @This();
22
3const builtin = @import("builtin");
4const native_os = builtin.os.tag;
5const is_windows = native_os == .windows;
6
7const std = @import("../../std.zig");3const std = @import("../../std.zig");
8const Io = std.Io;4const Io = std.Io;
9const File = std.Io.File;5const File = std.Io.File;
10const assert = std.debug.assert;6const assert = std.debug.assert;
11const windows = std.os.windows;
12const posix = std.posix;
137
8io: Io,
14file: File,9file: File,
15err: ?File.WriteError = null,10err: ?File.WriteError = null,
16mode: Writer.Mode = .positional,11mode: Mode = .positional,
17/// Tracks the true seek position in the file. To obtain the logical12/// Tracks the true seek position in the file. To obtain the logical
18/// position, add the buffer size to this value.13/// position, add the buffer size to this value.
19pos: u64 = 0,14pos: u64 = 0,
20sendfile_err: ?SendfileError = null,15write_file_err: ?WriteFileError = null,
21copy_file_range_err: ?CopyFileRangeError = null,16seek_err: ?SeekError = null,
22fcopyfile_err: ?FcopyfileError = null,
23seek_err: ?Writer.SeekError = null,
24interface: Io.Writer,17interface: Io.Writer,
2518
26pub const Mode = File.Reader.Mode;19pub const Mode = File.Reader.Mode;
2720
28pub const SendfileError = error{21pub const WriteFileError = error{
29 UnsupportedOperation,22 /// `out_fd` is an unconnected socket, or out_fd closed its read end.
30 SystemResources,
31 InputOutput,
32 BrokenPipe,23 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.
33 WouldBlock,27 WouldBlock,
34 Unexpected,28 /// Unspecified error while reading from in_fd.
35};29 InputOutput,
3630 /// Insufficient kernel memory to read from in_fd.
37pub const CopyFileRangeError = std.os.freebsd.CopyFileRangeError || std.os.linux.wrapped.CopyFileRangeError;31 SystemResources,
3832} || Io.Cancelable || Io.UnexpectedError;
39pub const FcopyfileError = error{
40 OperationNotSupported,
41 OutOfMemory,
42 Unexpected,
43};
4433
45pub const SeekError = Io.File.SeekError;34pub 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
51pub fn init(file: File, buffer: []u8) Writer {36pub fn init(file: File, buffer: []u8) Writer {
52 return .{37 return .{
53 .file = file,38 .file = file,
...@@ -91,445 +76,122 @@ pub fn moveToReader(w: *Writer) File.Reader {...@@ -91,445 +76,122 @@ pub fn moveToReader(w: *Writer) File.Reader {
9176
92pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {77pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
93 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));78 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
94 const handle = w.file.handle;79 switch (w.mode) {
95 const buffered = io_w.buffered();80 .positional, .positional_reading => return drainPositional(w, data, splat),
96 if (is_windows) switch (w.mode) {81 .streaming, .streaming_reading => return drainStreaming(w, data, splat),
97 .positional, .positional_reading => {82 .failure => return error.WriteFailed,
98 if (buffered.len != 0) {83 }
99 const n = windows.WriteFile(handle, buffered, w.pos) catch |err| {84}
100 w.err = err;85
101 return error.WriteFailed;86fn drainPositional(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
102 };87 const io = w.io;
103 w.pos += n;88 const header = w.interface.buffered();
104 return io_w.consume(n);89 const n = io.vtable.fileWritePositional(io.userdata, w.file, header, data, splat, w.pos) catch |err| switch (err) {
105 }90 error.Unseekable => {
106 for (data[0 .. data.len - 1]) |buf| {91 w.mode = w.mode.toStreaming();
107 if (buf.len == 0) continue;92 const pos = w.pos;
108 const n = windows.WriteFile(handle, buf, w.pos) catch |err| {93 if (pos != 0) {
109 w.err = err;94 w.pos = 0;
95 w.seekTo(@intCast(pos)) catch {
96 w.mode = .failure;
110 return error.WriteFailed;97 return error.WriteFailed;
111 };98 };
112 w.pos += n;
113 return io_w.consume(n);
114 }99 }
115 const pattern = data[data.len - 1];100 return 0;
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);
123 },101 },
124 .streaming, .streaming_reading => {102 else => |e| {
125 if (buffered.len != 0) {103 w.err = e;
126 const n = windows.WriteFile(handle, buffered, null) catch |err| {104 return error.WriteFailed;
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);
150 },105 },
151 .failure => return error.WriteFailed,
152 };106 };
153 var iovecs: [max_buffers_len]posix.iovec_const = undefined;107 w.pos += n;
154 var len: usize = 0;108 return w.interface.consume(n);
155 if (buffered.len > 0) {109}
156 iovecs[len] = .{ .base = buffered.ptr, .len = buffered.len };110
157 len += 1;111fn drainStreaming(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
158 }112 const io = w.io;
159 for (data[0 .. data.len - 1]) |d| {113 const header = w.interface.buffered();
160 if (d.len == 0) continue;114 const n = io.vtable.fileWriteStreaming(io.userdata, w.file, header, data, splat) catch |err| {
161 iovecs[len] = .{ .base = d.ptr, .len = d.len };115 w.err = err;
162 len += 1;116 return error.WriteFailed;
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 },
204 };117 };
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));
206 switch (w.mode) {124 switch (w.mode) {
207 .positional, .positional_reading => {125 .positional => return sendFilePositional(w, file_reader, limit),
208 const n = posix.pwritev(handle, iovecs[0..len], w.pos) catch |err| switch (err) {126 .positional_reading => return error.Unimplemented,
209 error.Unseekable => {127 .streaming => return sendFileStreaming(w, file_reader, limit),
210 w.mode = w.mode.toStreaming();128 .streaming_reading => return error.Unimplemented,
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 },
237 .failure => return error.WriteFailed,129 .failure => return error.WriteFailed,
238 }130 }
239}131}
240132
241pub fn sendFile(133fn sendFilePositional(w: *Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize {
242 io_w: *Io.Writer,134 const io = w.io;
243 file_reader: *Io.File.Reader,135 const header = w.interface.buffered();
244 limit: Io.Limit,136 const n = io.vtable.fileSendFilePositional(io.userdata, w.file, header, file_reader, limit, w.pos) catch |err| switch (err) {
245) Io.Writer.FileError!usize {137 error.Unseekable => {
246 const reader_buffered = file_reader.interface.buffered();138 w.mode = w.mode.toStreaming();
247 if (reader_buffered.len >= @intFromEnum(limit))139 const pos = w.pos;
248 return sendFileBuffered(io_w, file_reader, limit.slice(reader_buffered));140 if (pos != 0) {
249 const writer_buffered = io_w.buffered();141 w.pos = 0;
250 const file_limit = @intFromEnum(limit) - reader_buffered.len;142 w.seekTo(@intCast(pos)) catch {
251 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));143 w.mode = .failure;
252 const out_fd = w.file.handle;144 return error.WriteFailed;
253 const in_fd = file_reader.file.handle;145 };
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;
261 }146 }
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.
374 return 0;147 return 0;
375 }148 },
376 if (len == 0) {149 error.Canceled => {
377 file_reader.size = file_reader.pos;150 w.err = error.Canceled;
378 return error.EndOfStream;151 return error.WriteFailed;
379 }152 },
380 const consumed = io_w.consume(@bitCast(len));153 else => |e| {
381 file_reader.seekBy(@intCast(consumed)) catch return error.ReadFailed;154 w.write_file_err = e;
382 return consumed;155 return error.WriteFailed;
383 }156 },
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 => {},
434 };157 };
435 if (@TypeOf(copy_file_range) != void) cfr: {158 w.pos += n;
436 if (w.copy_file_range_err != null) break :cfr;159 return w.interface.consume(n);
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;
506}160}
507161
508fn sendFileBuffered(162fn sendFileStreaming(w: *Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize {
509 io_w: *Io.Writer,163 const io = w.io;
510 file_reader: *Io.File.Reader,164 const header = w.interface.buffered();
511 reader_buffered: []const u8,165 const n = io.vtable.fileSendFileStreaming(io.userdata, w.file, header, file_reader, limit) catch |err| switch (err) {
512) Io.Writer.FileError!usize {166 error.Canceled => {
513 const n = try drain(io_w, &.{reader_buffered}, 1);167 w.err = error.Canceled;
514 file_reader.seekBy(@intCast(n)) catch return error.ReadFailed;168 return error.WriteFailed;
515 return n;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);
516}177}
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 {
519 try w.interface.flush();180 try w.interface.flush();
520 try seekToUnbuffered(w, offset);181 try seekToUnbuffered(w, offset);
521}182}
522183
523/// Asserts that no data is currently buffered.184/// 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 {
525 assert(w.interface.buffered().len == 0);186 assert(w.interface.buffered().len == 0);
187 const io = w.io;
526 switch (w.mode) {188 switch (w.mode) {
527 .positional, .positional_reading => {189 .positional, .positional_reading => {
528 w.pos = offset;190 w.pos = offset;
529 },191 },
530 .streaming, .streaming_reading => {192 .streaming, .streaming_reading => {
531 if (w.seek_err) |err| return err;193 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| {
533 w.seek_err = err;195 w.seek_err = err;
534 return err;196 return err;
535 };197 };
...@@ -553,7 +215,7 @@ pub fn end(w: *Writer) EndError!void {...@@ -553,7 +215,7 @@ pub fn end(w: *Writer) EndError!void {
553 switch (w.mode) {215 switch (w.mode) {
554 .positional,216 .positional,
555 .positional_reading,217 .positional_reading,
556 => w.file.setEndPos(w.pos) catch |err| switch (err) {218 => w.file.setLength(w.pos) catch |err| switch (err) {
557 error.NonResizable => return,219 error.NonResizable => return,
558 else => |e| return e,220 else => |e| return e,
559 },221 },
lib/std/Io/Threaded.zig+874-14
...@@ -3,6 +3,7 @@ const Threaded = @This();...@@ -3,6 +3,7 @@ const Threaded = @This();
3const builtin = @import("builtin");3const builtin = @import("builtin");
4const native_os = builtin.os.tag;4const native_os = builtin.os.tag;
5const is_windows = native_os == .windows;5const is_windows = native_os == .windows;
6const is_darwin = native_os.isDarwin();
6const windows = std.os.windows;7const windows = std.os.windows;
7const ws2_32 = std.os.windows.ws2_32;8const ws2_32 = std.os.windows.ws2_32;
8const is_debug = builtin.mode == .Debug;9const is_debug = builtin.mode == .Debug;
...@@ -70,6 +71,10 @@ have_signal_handler: bool,...@@ -70,6 +71,10 @@ have_signal_handler: bool,
70old_sig_io: if (have_sig_io) posix.Sigaction else void,71old_sig_io: if (have_sig_io) posix.Sigaction else void,
71old_sig_pipe: if (have_sig_pipe) posix.Sigaction else void,72old_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
73pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum {78pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum {
74 enabled,79 enabled,
75 disabled,80 disabled,
...@@ -82,6 +87,33 @@ pub const Pid = if (native_os == .linux) enum(posix.pid_t) {...@@ -82,6 +87,33 @@ pub const Pid = if (native_os == .linux) enum(posix.pid_t) {
82 _,87 _,
83} else enum(u0) { unknown = 0 };88} 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
85const Thread = struct {117const Thread = struct {
86 /// The value that needs to be passed to pthread_kill or tgkill in order to118 /// The value that needs to be passed to pthread_kill or tgkill in order to
87 /// send a signal.119 /// send a signal.
...@@ -677,6 +709,8 @@ pub fn io(t: *Threaded) Io {...@@ -677,6 +709,8 @@ pub fn io(t: *Threaded) Io {
677 .fileClose = fileClose,709 .fileClose = fileClose,
678 .fileWriteStreaming = fileWriteStreaming,710 .fileWriteStreaming = fileWriteStreaming,
679 .fileWritePositional = fileWritePositional,711 .fileWritePositional = fileWritePositional,
712 .fileWriteFileStreaming = fileWriteFileStreaming,
713 .fileWriteFilePositional = fileWriteFilePositional,
680 .fileReadStreaming = fileReadStreaming,714 .fileReadStreaming = fileReadStreaming,
681 .fileReadPositional = fileReadPositional,715 .fileReadPositional = fileReadPositional,
682 .fileSeekBy = fileSeekBy,716 .fileSeekBy = fileSeekBy,
...@@ -797,6 +831,8 @@ pub fn ioBasic(t: *Threaded) Io {...@@ -797,6 +831,8 @@ pub fn ioBasic(t: *Threaded) Io {
797 .fileClose = fileClose,831 .fileClose = fileClose,
798 .fileWriteStreaming = fileWriteStreaming,832 .fileWriteStreaming = fileWriteStreaming,
799 .fileWritePositional = fileWritePositional,833 .fileWritePositional = fileWritePositional,
834 .fileWriteFileStreaming = fileWriteFileStreaming,
835 .fileWriteFilePositional = fileWriteFilePositional,
800 .fileReadStreaming = fileReadStreaming,836 .fileReadStreaming = fileReadStreaming,
801 .fileReadPositional = fileReadPositional,837 .fileReadPositional = fileReadPositional,
802 .fileSeekBy = fileSeekBy,838 .fileSeekBy = fileSeekBy,
...@@ -837,7 +873,7 @@ pub fn ioBasic(t: *Threaded) Io {...@@ -837,7 +873,7 @@ pub fn ioBasic(t: *Threaded) Io {
837 };873 };
838}874}
839875
840pub const socket_flags_unsupported = native_os.isDarwin() or native_os == .haiku;876pub const socket_flags_unsupported = is_darwin or native_os == .haiku;
841const have_accept4 = !socket_flags_unsupported;877const have_accept4 = !socket_flags_unsupported;
842const have_flock_open_flags = @hasField(posix.O, "EXLOCK");878const have_flock_open_flags = @hasField(posix.O, "EXLOCK");
843const have_networking = native_os != .wasi;879const have_networking = native_os != .wasi;
...@@ -853,6 +889,12 @@ const have_preadv = switch (native_os) {...@@ -853,6 +889,12 @@ const have_preadv = switch (native_os) {
853};889};
854const have_sig_io = posix.SIG != void and @hasField(posix.SIG, "IO");890const have_sig_io = posix.SIG != void and @hasField(posix.SIG, "IO");
855const have_sig_pipe = posix.SIG != void and @hasField(posix.SIG, "PIPE");891const 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
857const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat;899const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat;
858const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat;900const 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...@@ -860,6 +902,18 @@ const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.syste
860const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek;902const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek;
861const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv;903const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv;
862const ftruncate_sym = if (posix.lfs64_abi) posix.system.ftruncate64 else posix.system.ftruncate;904const 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
864/// Trailing data:918/// Trailing data:
865/// 1. context919/// 1. context
...@@ -5896,27 +5950,828 @@ fn openSelfExe(userdata: ?*anyopaque, flags: Io.File.OpenFlags) Io.File.OpenSelf...@@ -5896,27 +5950,828 @@ fn openSelfExe(userdata: ?*anyopaque, flags: Io.File.OpenFlags) Io.File.OpenSelf
5896fn fileWritePositional(5950fn fileWritePositional(
5897 userdata: ?*anyopaque,5951 userdata: ?*anyopaque,
5898 file: Io.File,5952 file: Io.File,
5899 buffer: [][]const u8,5953 header: []const u8,
5954 data: []const []const u8,
5955 splat: usize,
5900 offset: u64,5956 offset: u64,
5901) Io.File.WritePositionalError!usize {5957) Io.File.WritePositionalError!usize {
5902 const t: *Threaded = @ptrCast(@alignCast(userdata));5958 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();
5904 while (true) {6034 while (true) {
5905 _ = file;6035 const rc = pwritev_sym(file.handle, &iovecs, iovlen, @bitCast(offset));
5906 _ = buffer;6036 switch (posix.errno(rc)) {
5907 _ = offset;6037 .SUCCESS => {
5908 @panic("TODO implement fileWritePositional");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 }
5909 }6069 }
5910}6070}
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 {
5913 const t: *Threaded = @ptrCast(@alignCast(userdata));6079 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();
5915 while (true) {6152 while (true) {
5916 _ = file;6153 const rc = posix.system.writev(file.handle, &iovecs, iovlen);
5917 _ = buffer;6154 switch (posix.errno(rc)) {
5918 @panic("TODO implement fileWriteStreaming");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;
5919 }6772 }
6773
6774 return error.Unimplemented;
5920}6775}
59216776
5922fn nowPosix(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {6777fn nowPosix(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
...@@ -7847,6 +8702,7 @@ fn netWritePosix(...@@ -7847,6 +8702,7 @@ fn netWritePosix(
7847 },8702 },
7848 };8703 };
7849 const flags = posix.MSG.NOSIGNAL;8704 const flags = posix.MSG.NOSIGNAL;
8705
7850 try current_thread.beginSyscall();8706 try current_thread.beginSyscall();
7851 while (true) {8707 while (true) {
7852 const rc = posix.system.sendmsg(fd, &msg, flags);8708 const rc = posix.system.sendmsg(fd, &msg, flags);
...@@ -8010,7 +8866,11 @@ fn netWriteUnavailable(...@@ -8010,7 +8866,11 @@ fn netWriteUnavailable(
8010 return error.NetworkDown;8866 return error.NetworkDown;
8011}8867}
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 {
8014 // OS checks ptr addr before length so zero length vectors must be omitted.8874 // OS checks ptr addr before length so zero length vectors must be omitted.
8015 if (bytes.len == 0) return;8875 if (bytes.len == 0) return;
8016 if (v.len - i.* == 0) return;8876 if (v.len - i.* == 0) return;
...@@ -8336,7 +9196,7 @@ fn netLookupFallible(...@@ -8336,7 +9196,7 @@ fn netLookupFallible(
8336 // TODO use dnsres_getaddrinfo9196 // TODO use dnsres_getaddrinfo
8337 }9197 }
83389198
8339 if (native_os.isDarwin()) {9199 if (is_darwin) {
8340 // TODO use CFHostStartInfoResolution / CFHostCancelInfoResolution9200 // TODO use CFHostStartInfoResolution / CFHostCancelInfoResolution
8341 }9201 }
83429202
lib/std/os.zig-1
...@@ -30,7 +30,6 @@ pub const uefi = @import("os/uefi.zig");...@@ -30,7 +30,6 @@ pub const uefi = @import("os/uefi.zig");
30pub const wasi = @import("os/wasi.zig");30pub const wasi = @import("os/wasi.zig");
31pub const emscripten = @import("os/emscripten.zig");31pub const emscripten = @import("os/emscripten.zig");
32pub const windows = @import("os/windows.zig");32pub const windows = @import("os/windows.zig");
33pub const freebsd = @import("os/freebsd.zig");
3433
35test {34test {
36 _ = linux;35 _ = 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 {...@@ -9872,165 +9872,3 @@ pub const cmsghdr = extern struct {
9872 level: i32,9872 level: i32,
9873 type: i32,9873 type: i32,
9874};9874};
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" {...@@ -401,14 +401,6 @@ test "futex2_requeue" {
401 try expectEqual(0, rc);401 try expectEqual(0, rc);
402}402}
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
412test {404test {
413 _ = linux.IoUring;405 _ = linux.IoUring;
414}406}
lib/std/posix.zig-166
...@@ -1157,80 +1157,6 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {...@@ -1157,80 +1157,6 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {
1157 }1157 }
1158}1158}
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
1234pub const PWriteError = WriteError || error{Unseekable};1160pub const PWriteError = WriteError || error{Unseekable};
12351161
1236/// Write to a file descriptor, with a position offset.1162/// Write to a file descriptor, with a position offset.
...@@ -4384,98 +4310,6 @@ pub fn send(...@@ -4384,98 +4310,6 @@ pub fn send(
4384 };4310 };
4385}4311}
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
4479pub const PollError = error{4313pub const PollError = error{
4480 /// The network subsystem has failed.4314 /// The network subsystem has failed.
4481 NetworkDown,4315 NetworkDown,