| author | |
| committer | |
| log | ba999d608c304c37667175b35e4a64f87d90351c |
| tree | 4557419b972c7d47aa1b3a79f8e1909f1bf9228a |
| parent | 814480db7cd0ab1d8441919223a88ee411f54ea8 |
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 | 662 | futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void, |
| 663 | 663 | futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void, |
| 664 | 664 | |
| 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, | |
| 667 | 667 | dirMakeOpenPath: *const fn (?*anyopaque, Dir, []const u8, Dir.OpenOptions) Dir.MakeOpenPathError!Dir, |
| 668 | 668 | dirStat: *const fn (?*anyopaque, Dir) Dir.StatError!Dir.Stat, |
| 669 | 669 | dirStatPath: *const fn (?*anyopaque, Dir, []const u8, Dir.StatPathOptions) Dir.StatPathError!File.Stat, |
| ... | ... | @@ -687,8 +687,10 @@ pub const VTable = struct { |
| 687 | 687 | fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat, |
| 688 | 688 | fileLength: *const fn (?*anyopaque, File) File.LengthError!u64, |
| 689 | 689 | 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, | |
| 692 | 694 | /// Returns 0 on end of stream. |
| 693 | 695 | fileReadStreaming: *const fn (?*anyopaque, File, data: [][]u8) File.Reader.Error!usize, |
| 694 | 696 | /// Returns 0 on end of stream. |
| ... | ... | @@ -724,6 +726,7 @@ pub const VTable = struct { |
| 724 | 726 | /// Returns 0 on end of stream. |
| 725 | 727 | netRead: *const fn (?*anyopaque, src: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize, |
| 726 | 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 | 730 | netClose: *const fn (?*anyopaque, handle: net.Socket.Handle) void, |
| 728 | 731 | netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface, |
| 729 | 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 | 652 | NoSpaceLeft, |
| 653 | 653 | FileSystem, |
| 654 | 654 | DeviceBusy, |
| 655 | ProcessNotFound, | |
| 656 | 655 | SharingViolation, |
| 657 | 656 | PipeBusy, |
| 658 | 657 | /// Windows: file paths provided by the user must be valid WTF-8. |
| ... | ... | @@ -1038,7 +1037,6 @@ pub const DeleteTreeError = error{ |
| 1038 | 1037 | FileSystem, |
| 1039 | 1038 | FileBusy, |
| 1040 | 1039 | DeviceBusy, |
| 1041 | ProcessNotFound, | |
| 1042 | 1040 | /// One of the path components was not a directory. |
| 1043 | 1041 | /// This error is unreachable if `sub_path` does not contain a path separator. |
| 1044 | 1042 | NotDir, |
lib/std/Io/File.zig+16-8| ... | ... | @@ -198,7 +198,6 @@ pub const OpenError = error{ |
| 198 | 198 | NoDevice, |
| 199 | 199 | /// On Windows, `\\server` or `\\server\share` was not found. |
| 200 | 200 | NetworkNotFound, |
| 201 | ProcessNotFound, | |
| 202 | 201 | /// On Windows, antivirus software is enabled by default. It can be |
| 203 | 202 | /// disabled, but Windows Update sometimes ignores the user's preference |
| 204 | 203 | /// 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 | 476 | return io.vtable.fileReadPositional(io.userdata, file, buffer, offset); |
| 478 | 477 | } |
| 479 | 478 | |
| 480 | pub const WriteStreamingError = error{} || Io.UnexpectedError || Io.Cancelable; | |
| 481 | ||
| 482 | pub fn writeStreaming(file: File, io: Io, buffer: [][]const u8) WriteStreamingError!usize { | |
| 483 | return file.fileWriteStreaming(io, buffer); | |
| 484 | } | |
| 485 | ||
| 486 | pub const WritePositionalError = WriteStreamingError || error{Unseekable}; | |
| 479 | pub const WritePositionalError = Writer.Error || error{Unseekable}; | |
| 487 | 480 | |
| 488 | 481 | pub fn writePositional(file: File, io: Io, buffer: [][]const u8, offset: u64) WritePositionalError!usize { |
| 489 | 482 | return io.vtable.fileWritePositional(io.userdata, file, buffer, offset); |
| 490 | 483 | } |
| 491 | 484 | |
| 485 | pub 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 | 498 | /// Opens a file for reading or writing, without attempting to create a new |
| 493 | 499 | /// file, based on an absolute path. |
| 494 | 500 | /// |
| ... | ... | @@ -511,6 +517,8 @@ pub const SeekError = error{ |
| 511 | 517 | AccessDenied, |
| 512 | 518 | } || Io.Cancelable || Io.UnexpectedError; |
| 513 | 519 | |
| 520 | pub const WriteFilePositionalError = Writer.WriteFileError || error{Unseekable}; | |
| 521 | ||
| 514 | 522 | /// Defaults to positional reading; falls back to streaming. |
| 515 | 523 | /// |
| 516 | 524 | /// 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 | 17 | io: Io, |
| 18 | 18 | file: File, |
| 19 | 19 | err: ?Error = null, |
| 20 | mode: Reader.Mode = .positional, | |
| 20 | mode: Mode = .positional, | |
| 21 | 21 | /// Tracks the true seek position in the file. To obtain the logical |
| 22 | 22 | /// position, use `logicalPos`. |
| 23 | 23 | pos: u64 = 0, |
| 24 | 24 | size: ?u64 = null, |
| 25 | 25 | size_err: ?SizeError = null, |
| 26 | seek_err: ?Reader.SeekError = null, | |
| 26 | seek_err: ?SeekError = null, | |
| 27 | 27 | interface: Io.Reader, |
| 28 | 28 | |
| 29 | 29 | pub const Error = error{ |
| ... | ... | @@ -37,15 +37,12 @@ pub const Error = error{ |
| 37 | 37 | /// trying to read a directory file descriptor as if it were a file. |
| 38 | 38 | NotOpenForReading, |
| 39 | 39 | 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. | |
| 42 | 42 | WouldBlock, |
| 43 | 43 | /// In WASI, this error occurs when the file descriptor does |
| 44 | 44 | /// not hold the required rights to read from it. |
| 45 | 45 | AccessDenied, |
| 46 | /// This error occurs in Linux if the process to be read from | |
| 47 | /// no longer exists. | |
| 48 | ProcessNotFound, | |
| 49 | 46 | /// Unable to read file due to lock. |
| 50 | 47 | LockViolation, |
| 51 | 48 | } || Io.Cancelable || Io.UnexpectedError; |
| ... | ... | @@ -93,9 +90,9 @@ pub const Mode = enum { |
| 93 | 90 | pub fn initInterface(buffer: []u8) Io.Reader { |
| 94 | 91 | return .{ |
| 95 | 92 | .vtable = &.{ |
| 96 | .stream = Reader.stream, | |
| 97 | .discard = Reader.discard, | |
| 98 | .readVec = Reader.readVec, | |
| 93 | .stream = stream, | |
| 94 | .discard = discard, | |
| 95 | .readVec = readVec, | |
| 99 | 96 | }, |
| 100 | 97 | .buffer = buffer, |
| 101 | 98 | .seek = 0, |
| ... | ... | @@ -153,7 +150,7 @@ pub fn getSize(r: *Reader) SizeError!u64 { |
| 153 | 150 | }; |
| 154 | 151 | } |
| 155 | 152 | |
| 156 | pub fn seekBy(r: *Reader, offset: i64) Reader.SeekError!void { | |
| 153 | pub fn seekBy(r: *Reader, offset: i64) SeekError!void { | |
| 157 | 154 | const io = r.io; |
| 158 | 155 | switch (r.mode) { |
| 159 | 156 | .positional, .positional_reading => { |
| ... | ... | @@ -183,7 +180,7 @@ pub fn seekBy(r: *Reader, offset: i64) Reader.SeekError!void { |
| 183 | 180 | } |
| 184 | 181 | |
| 185 | 182 | /// Repositions logical read offset relative to the beginning of the file. |
| 186 | pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void { | |
| 183 | pub fn seekTo(r: *Reader, offset: u64) SeekError!void { | |
| 187 | 184 | const io = r.io; |
| 188 | 185 | switch (r.mode) { |
| 189 | 186 | .positional, .positional_reading => { |
| ... | ... | @@ -191,7 +188,7 @@ pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void { |
| 191 | 188 | }, |
| 192 | 189 | .streaming, .streaming_reading => { |
| 193 | 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 | 192 | if (r.seek_err) |err| return err; |
| 196 | 193 | io.vtable.fileSeekTo(io.userdata, r.file, offset) catch |err| { |
| 197 | 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 | 221 | return streamMode(r, w, limit, r.mode); |
| 225 | 222 | } |
| 226 | 223 | |
| 227 | pub fn streamMode(r: *Reader, w: *Io.Writer, limit: Io.Limit, mode: Reader.Mode) Io.Reader.StreamError!usize { | |
| 224 | pub fn streamMode(r: *Reader, w: *Io.Writer, limit: Io.Limit, mode: Mode) Io.Reader.StreamError!usize { | |
| 228 | 225 | switch (mode) { |
| 229 | 226 | .positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) { |
| 230 | 227 | error.Unimplemented => { |
lib/std/Io/File/Writer.zig+100-438| ... | ... | @@ -1,53 +1,38 @@ |
| 1 | 1 | const Writer = @This(); |
| 2 | 2 | |
| 3 | const builtin = @import("builtin"); | |
| 4 | const native_os = builtin.os.tag; | |
| 5 | const is_windows = native_os == .windows; | |
| 6 | ||
| 7 | 3 | const std = @import("../../std.zig"); |
| 8 | 4 | const Io = std.Io; |
| 9 | 5 | const File = std.Io.File; |
| 10 | 6 | const assert = std.debug.assert; |
| 11 | const windows = std.os.windows; | |
| 12 | const posix = std.posix; | |
| 13 | 7 | |
| 8 | io: Io, | |
| 14 | 9 | file: File, |
| 15 | 10 | err: ?File.WriteError = null, |
| 16 | mode: Writer.Mode = .positional, | |
| 11 | mode: Mode = .positional, | |
| 17 | 12 | /// Tracks the true seek position in the file. To obtain the logical |
| 18 | 13 | /// position, add the buffer size to this value. |
| 19 | 14 | pos: u64 = 0, |
| 20 | sendfile_err: ?SendfileError = null, | |
| 21 | copy_file_range_err: ?CopyFileRangeError = null, | |
| 22 | fcopyfile_err: ?FcopyfileError = null, | |
| 23 | seek_err: ?Writer.SeekError = null, | |
| 15 | write_file_err: ?WriteFileError = null, | |
| 16 | seek_err: ?SeekError = null, | |
| 24 | 17 | interface: Io.Writer, |
| 25 | 18 | |
| 26 | 19 | pub const Mode = File.Reader.Mode; |
| 27 | 20 | |
| 28 | pub const SendfileError = error{ | |
| 29 | UnsupportedOperation, | |
| 30 | SystemResources, | |
| 31 | InputOutput, | |
| 21 | pub const WriteFileError = error{ | |
| 22 | /// `out_fd` is an unconnected socket, or out_fd closed its read end. | |
| 32 | 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 | 27 | WouldBlock, |
| 34 | Unexpected, | |
| 35 | }; | |
| 36 | ||
| 37 | pub const CopyFileRangeError = std.os.freebsd.CopyFileRangeError || std.os.linux.wrapped.CopyFileRangeError; | |
| 38 | ||
| 39 | pub 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; | |
| 44 | 33 | |
| 45 | 34 | pub const SeekError = Io.File.SeekError; |
| 46 | 35 | |
| 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. | |
| 49 | const max_buffers_len = 16; | |
| 50 | ||
| 51 | 36 | pub fn init(file: File, buffer: []u8) Writer { |
| 52 | 37 | return .{ |
| 53 | 38 | .file = file, |
| ... | ... | @@ -91,445 +76,122 @@ pub fn moveToReader(w: *Writer) File.Reader { |
| 91 | 76 | |
| 92 | 77 | pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { |
| 93 | 78 | 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 | ||
| 86 | fn 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; | |
| 110 | 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]; | |
| 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; | |
| 123 | 101 | }, |
| 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; | |
| 150 | 105 | }, |
| 151 | .failure => return error.WriteFailed, | |
| 152 | 106 | }; |
| 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 | ||
| 111 | fn 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; | |
| 204 | 117 | }; |
| 205 | if (len == 0) return 0; | |
| 118 | w.pos += n; | |
| 119 | return w.interface.consume(n); | |
| 120 | } | |
| 121 | ||
| 122 | pub 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 | 124 | 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, | |
| 237 | 129 | .failure => return error.WriteFailed, |
| 238 | 130 | } |
| 239 | 131 | } |
| 240 | 132 | |
| 241 | pub 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; | |
| 133 | fn 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 | }; | |
| 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 | 147 | 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 | }, | |
| 434 | 157 | }; |
| 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); | |
| 506 | 160 | } |
| 507 | 161 | |
| 508 | fn 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; | |
| 162 | fn 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); | |
| 516 | 177 | } |
| 517 | 178 | |
| 518 | pub fn seekTo(w: *Writer, offset: u64) (Writer.SeekError || Io.Writer.Error)!void { | |
| 179 | pub fn seekTo(w: *Writer, offset: u64) (SeekError || Io.Writer.Error)!void { | |
| 519 | 180 | try w.interface.flush(); |
| 520 | 181 | try seekToUnbuffered(w, offset); |
| 521 | 182 | } |
| 522 | 183 | |
| 523 | 184 | /// Asserts that no data is currently buffered. |
| 524 | pub fn seekToUnbuffered(w: *Writer, offset: u64) Writer.SeekError!void { | |
| 185 | pub fn seekToUnbuffered(w: *Writer, offset: u64) SeekError!void { | |
| 525 | 186 | assert(w.interface.buffered().len == 0); |
| 187 | const io = w.io; | |
| 526 | 188 | switch (w.mode) { |
| 527 | 189 | .positional, .positional_reading => { |
| 528 | 190 | w.pos = offset; |
| 529 | 191 | }, |
| 530 | 192 | .streaming, .streaming_reading => { |
| 531 | 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 | 195 | w.seek_err = err; |
| 534 | 196 | return err; |
| 535 | 197 | }; |
| ... | ... | @@ -553,7 +215,7 @@ pub fn end(w: *Writer) EndError!void { |
| 553 | 215 | switch (w.mode) { |
| 554 | 216 | .positional, |
| 555 | 217 | .positional_reading, |
| 556 | => w.file.setEndPos(w.pos) catch |err| switch (err) { | |
| 218 | => w.file.setLength(w.pos) catch |err| switch (err) { | |
| 557 | 219 | error.NonResizable => return, |
| 558 | 220 | else => |e| return e, |
| 559 | 221 | }, |
lib/std/Io/Threaded.zig+874-14| ... | ... | @@ -3,6 +3,7 @@ const Threaded = @This(); |
| 3 | 3 | const builtin = @import("builtin"); |
| 4 | 4 | const native_os = builtin.os.tag; |
| 5 | 5 | const is_windows = native_os == .windows; |
| 6 | const is_darwin = native_os.isDarwin(); | |
| 6 | 7 | const windows = std.os.windows; |
| 7 | 8 | const ws2_32 = std.os.windows.ws2_32; |
| 8 | 9 | const is_debug = builtin.mode == .Debug; |
| ... | ... | @@ -70,6 +71,10 @@ have_signal_handler: bool, |
| 70 | 71 | old_sig_io: if (have_sig_io) posix.Sigaction else void, |
| 71 | 72 | old_sig_pipe: if (have_sig_pipe) posix.Sigaction else void, |
| 72 | 73 | |
| 74 | use_sendfile: UseSendfile = .default, | |
| 75 | use_copy_file_range: UseCopyFileRange = .default, | |
| 76 | use_fcopyfile: UseFcopyfile = .default, | |
| 77 | ||
| 73 | 78 | pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum { |
| 74 | 79 | enabled, |
| 75 | 80 | disabled, |
| ... | ... | @@ -82,6 +87,33 @@ pub const Pid = if (native_os == .linux) enum(posix.pid_t) { |
| 82 | 87 | _, |
| 83 | 88 | } else enum(u0) { unknown = 0 }; |
| 84 | 89 | |
| 90 | pub 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 | ||
| 99 | pub 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 | ||
| 108 | pub 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 | ||
| 85 | 117 | const Thread = struct { |
| 86 | 118 | /// The value that needs to be passed to pthread_kill or tgkill in order to |
| 87 | 119 | /// send a signal. |
| ... | ... | @@ -677,6 +709,8 @@ pub fn io(t: *Threaded) Io { |
| 677 | 709 | .fileClose = fileClose, |
| 678 | 710 | .fileWriteStreaming = fileWriteStreaming, |
| 679 | 711 | .fileWritePositional = fileWritePositional, |
| 712 | .fileWriteFileStreaming = fileWriteFileStreaming, | |
| 713 | .fileWriteFilePositional = fileWriteFilePositional, | |
| 680 | 714 | .fileReadStreaming = fileReadStreaming, |
| 681 | 715 | .fileReadPositional = fileReadPositional, |
| 682 | 716 | .fileSeekBy = fileSeekBy, |
| ... | ... | @@ -797,6 +831,8 @@ pub fn ioBasic(t: *Threaded) Io { |
| 797 | 831 | .fileClose = fileClose, |
| 798 | 832 | .fileWriteStreaming = fileWriteStreaming, |
| 799 | 833 | .fileWritePositional = fileWritePositional, |
| 834 | .fileWriteFileStreaming = fileWriteFileStreaming, | |
| 835 | .fileWriteFilePositional = fileWriteFilePositional, | |
| 800 | 836 | .fileReadStreaming = fileReadStreaming, |
| 801 | 837 | .fileReadPositional = fileReadPositional, |
| 802 | 838 | .fileSeekBy = fileSeekBy, |
| ... | ... | @@ -837,7 +873,7 @@ pub fn ioBasic(t: *Threaded) Io { |
| 837 | 873 | }; |
| 838 | 874 | } |
| 839 | 875 | |
| 840 | pub const socket_flags_unsupported = native_os.isDarwin() or native_os == .haiku; | |
| 876 | pub const socket_flags_unsupported = is_darwin or native_os == .haiku; | |
| 841 | 877 | const have_accept4 = !socket_flags_unsupported; |
| 842 | 878 | const have_flock_open_flags = @hasField(posix.O, "EXLOCK"); |
| 843 | 879 | const have_networking = native_os != .wasi; |
| ... | ... | @@ -853,6 +889,12 @@ const have_preadv = switch (native_os) { |
| 853 | 889 | }; |
| 854 | 890 | const have_sig_io = posix.SIG != void and @hasField(posix.SIG, "IO"); |
| 855 | 891 | const have_sig_pipe = posix.SIG != void and @hasField(posix.SIG, "PIPE"); |
| 892 | const have_sendfile = if (builtin.link_libc) @TypeOf(std.c.sendfile) != void else native_os == .linux; | |
| 893 | const have_copy_file_range = switch (native_os) { | |
| 894 | .linux, .freebsd => true, | |
| 895 | else => false, | |
| 896 | }; | |
| 897 | const have_fcopyfile = is_darwin; | |
| 856 | 898 | |
| 857 | 899 | const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat; |
| 858 | 900 | const 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 | 902 | const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek; |
| 861 | 903 | const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv; |
| 862 | 904 | const ftruncate_sym = if (posix.lfs64_abi) posix.system.ftruncate64 else posix.system.ftruncate; |
| 905 | const pwritev_sym = if (posix.lfs64_abi) posix.system.pwritev64 else posix.system.pwritev; | |
| 906 | const sendfile_sym = if (posix.lfs64_abi) posix.system.sendfile64 else posix.system.sendfile; | |
| 907 | const 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 | }); | |
| 916 | const linux_copy_file_range_sys = if (linux_copy_file_range_use_c) std.c else std.os.linux; | |
| 863 | 917 | |
| 864 | 918 | /// Trailing data: |
| 865 | 919 | /// 1. context |
| ... | ... | @@ -5896,27 +5950,828 @@ fn openSelfExe(userdata: ?*anyopaque, flags: Io.File.OpenFlags) Io.File.OpenSelf |
| 5896 | 5950 | fn fileWritePositional( |
| 5897 | 5951 | userdata: ?*anyopaque, |
| 5898 | 5952 | file: Io.File, |
| 5899 | buffer: [][]const u8, | |
| 5953 | header: []const u8, | |
| 5954 | data: []const []const u8, | |
| 5955 | splat: usize, | |
| 5900 | 5956 | offset: u64, |
| 5901 | 5957 | ) Io.File.WritePositionalError!usize { |
| 5902 | 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 | 6034 | 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 | } | |
| 5909 | 6069 | } |
| 5910 | 6070 | } |
| 5911 | 6071 | |
| 5912 | fn fileWriteStreaming(userdata: ?*anyopaque, file: Io.File, buffer: [][]const u8) Io.File.WriteStreamingError!usize { | |
| 6072 | fn 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 | 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 | 6152 | 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 | ||
| 6188 | fn 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 | ||
| 6584 | fn 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 | } |
| 5921 | 6776 | |
| 5922 | 6777 | fn nowPosix(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp { |
| ... | ... | @@ -7847,6 +8702,7 @@ fn netWritePosix( |
| 7847 | 8702 | }, |
| 7848 | 8703 | }; |
| 7849 | 8704 | const flags = posix.MSG.NOSIGNAL; |
| 8705 | ||
| 7850 | 8706 | try current_thread.beginSyscall(); |
| 7851 | 8707 | while (true) { |
| 7852 | 8708 | const rc = posix.system.sendmsg(fd, &msg, flags); |
| ... | ... | @@ -8010,7 +8866,11 @@ fn netWriteUnavailable( |
| 8010 | 8866 | return error.NetworkDown; |
| 8011 | 8867 | } |
| 8012 | 8868 | |
| 8013 | fn 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. | |
| 8871 | const iovlen_t = @FieldType(posix.msghdr_const, "iovlen"); | |
| 8872 | ||
| 8873 | fn addBuf(v: []posix.iovec_const, i: *iovlen_t, bytes: []const u8) void { | |
| 8014 | 8874 | // OS checks ptr addr before length so zero length vectors must be omitted. |
| 8015 | 8875 | if (bytes.len == 0) return; |
| 8016 | 8876 | if (v.len - i.* == 0) return; |
| ... | ... | @@ -8336,7 +9196,7 @@ fn netLookupFallible( |
| 8336 | 9196 | // TODO use dnsres_getaddrinfo |
| 8337 | 9197 | } |
| 8338 | 9198 | |
| 8339 | if (native_os.isDarwin()) { | |
| 9199 | if (is_darwin) { | |
| 8340 | 9200 | // TODO use CFHostStartInfoResolution / CFHostCancelInfoResolution |
| 8341 | 9201 | } |
| 8342 | 9202 |
lib/std/os.zig-1| ... | ... | @@ -30,7 +30,6 @@ pub const uefi = @import("os/uefi.zig"); |
| 30 | 30 | pub const wasi = @import("os/wasi.zig"); |
| 31 | 31 | pub const emscripten = @import("os/emscripten.zig"); |
| 32 | 32 | pub const windows = @import("os/windows.zig"); |
| 33 | pub const freebsd = @import("os/freebsd.zig"); | |
| 34 | 33 | |
| 35 | 34 | test { |
| 36 | 35 | _ = linux; |
lib/std/os/freebsd.zig deleted-50| ... | ... | @@ -1,50 +0,0 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const fd_t = std.c.fd_t; | |
| 3 | const off_t = std.c.off_t; | |
| 4 | const unexpectedErrno = std.posix.unexpectedErrno; | |
| 5 | const errno = std.posix.errno; | |
| 6 | const builtin = @import("builtin"); | |
| 7 | ||
| 8 | pub 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 | ||
| 36 | 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 { | |
| 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 | 9872 | level: i32, |
| 9873 | 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. | |
| 9878 | pub 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 | 401 | try expectEqual(0, rc); |
| 402 | 402 | } |
| 403 | 403 | |
| 404 | test "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 | ||
| 412 | 404 | test { |
| 413 | 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 | 1157 | } |
| 1158 | 1158 | } |
| 1159 | 1159 | |
| 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. | |
| 1181 | pub 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 | ||
| 1234 | 1160 | pub const PWriteError = WriteError || error{Unseekable}; |
| 1235 | 1161 | |
| 1236 | 1162 | /// Write to a file descriptor, with a position offset. |
| ... | ... | @@ -4384,98 +4310,6 @@ pub fn send( |
| 4384 | 4310 | }; |
| 4385 | 4311 | } |
| 4386 | 4312 | |
| 4387 | pub 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)`. | |
| 4429 | pub 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 | ||
| 4479 | 4313 | pub const PollError = error{ |
| 4480 | 4314 | /// The network subsystem has failed. |
| 4481 | 4315 | NetworkDown, |