diff --git a/lib/std/Io.zig b/lib/std/Io.zig index e0e0e4254c..edd66242ff 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -662,8 +662,8 @@ pub const VTable = struct { conditionWaitUncancelable: *const fn (?*anyopaque, cond: *Condition, mutex: *Mutex) void, conditionWake: *const fn (?*anyopaque, cond: *Condition, wake: Condition.Wake) void, - dirMake: *const fn (?*anyopaque, Dir, []const u8, Dir.Mode) Dir.MakeError!void, - dirMakePath: *const fn (?*anyopaque, Dir, []const u8, Dir.Mode) Dir.MakePathError!Dir.MakePathStatus, + dirMake: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.MakeError!void, + dirMakePath: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.MakePathError!Dir.MakePathStatus, dirMakeOpenPath: *const fn (?*anyopaque, Dir, []const u8, Dir.OpenOptions) Dir.MakeOpenPathError!Dir, dirStat: *const fn (?*anyopaque, Dir) Dir.StatError!Dir.Stat, dirStatPath: *const fn (?*anyopaque, Dir, []const u8, Dir.StatPathOptions) Dir.StatPathError!File.Stat, @@ -687,8 +687,10 @@ pub const VTable = struct { fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat, fileLength: *const fn (?*anyopaque, File) File.LengthError!u64, fileClose: *const fn (?*anyopaque, File) void, - fileWriteStreaming: *const fn (?*anyopaque, File, buffer: [][]const u8) File.WriteStreamingError!usize, - fileWritePositional: *const fn (?*anyopaque, File, buffer: [][]const u8, offset: u64) File.WritePositionalError!usize, + fileWriteStreaming: *const fn (?*anyopaque, File, header: []const u8, data: []const []const u8, splat: usize) File.Writer.Error!usize, + fileWritePositional: *const fn (?*anyopaque, File, header: []const u8, data: []const []const u8, splat: usize, offset: u64) File.WritePositionalError!usize, + fileWriteFileStreaming: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit) File.Writer.WriteFileError!usize, + fileWriteFilePositional: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit, offset: u64) File.WriteFilePositionalError!usize, /// Returns 0 on end of stream. fileReadStreaming: *const fn (?*anyopaque, File, data: [][]u8) File.Reader.Error!usize, /// Returns 0 on end of stream. @@ -724,6 +726,7 @@ pub const VTable = struct { /// Returns 0 on end of stream. netRead: *const fn (?*anyopaque, src: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize, netWrite: *const fn (?*anyopaque, dest: net.Socket.Handle, header: []const u8, data: []const []const u8, splat: usize) net.Stream.Writer.Error!usize, + netWriteFile: *const fn (?*anyopaque, net.Socket.Handle, header: []const u8, *Io.File.Reader, Io.Limit) net.Stream.WriteFileError!usize, netClose: *const fn (?*anyopaque, handle: net.Socket.Handle) void, netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface, netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name, diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 51fe6bbb5f..348950e20a 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -652,7 +652,6 @@ pub const RealPathError = error{ NoSpaceLeft, FileSystem, DeviceBusy, - ProcessNotFound, SharingViolation, PipeBusy, /// Windows: file paths provided by the user must be valid WTF-8. @@ -1038,7 +1037,6 @@ pub const DeleteTreeError = error{ FileSystem, FileBusy, DeviceBusy, - ProcessNotFound, /// One of the path components was not a directory. /// This error is unreachable if `sub_path` does not contain a path separator. NotDir, diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index 2c1b292b62..46235d73f2 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -198,7 +198,6 @@ pub const OpenError = error{ NoDevice, /// On Windows, `\\server` or `\\server\share` was not found. NetworkNotFound, - ProcessNotFound, /// On Windows, antivirus software is enabled by default. It can be /// disabled, but Windows Update sometimes ignores the user's preference /// 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 return io.vtable.fileReadPositional(io.userdata, file, buffer, offset); } -pub const WriteStreamingError = error{} || Io.UnexpectedError || Io.Cancelable; - -pub fn writeStreaming(file: File, io: Io, buffer: [][]const u8) WriteStreamingError!usize { - return file.fileWriteStreaming(io, buffer); -} - -pub const WritePositionalError = WriteStreamingError || error{Unseekable}; +pub const WritePositionalError = Writer.Error || error{Unseekable}; pub fn writePositional(file: File, io: Io, buffer: [][]const u8, offset: u64) WritePositionalError!usize { return io.vtable.fileWritePositional(io.userdata, file, buffer, offset); } +pub const WriteFileStreamingError = error{ + /// `out_fd` is an unconnected socket, or out_fd closed its read end. + BrokenPipe, + /// Descriptor is not valid or locked, or an mmap(2)-like operation is not available for in_fd. + UnsupportedOperation, + /// Nonblocking I/O has been selected but the write would block. + WouldBlock, + /// Unspecified error while reading from in_fd. + InputOutput, + /// Insufficient kernel memory to read from in_fd. + SystemResources, +} || Io.Cancelable || Io.UnexpectedError; + /// Opens a file for reading or writing, without attempting to create a new /// file, based on an absolute path. /// @@ -511,6 +517,8 @@ pub const SeekError = error{ AccessDenied, } || Io.Cancelable || Io.UnexpectedError; +pub const WriteFilePositionalError = Writer.WriteFileError || error{Unseekable}; + /// Defaults to positional reading; falls back to streaming. /// /// Positional is more threadsafe, since the global seek position is not diff --git a/lib/std/Io/File/Reader.zig b/lib/std/Io/File/Reader.zig index 4af77604fa..8b2074a0aa 100644 --- a/lib/std/Io/File/Reader.zig +++ b/lib/std/Io/File/Reader.zig @@ -17,13 +17,13 @@ const assert = std.debug.assert; io: Io, file: File, err: ?Error = null, -mode: Reader.Mode = .positional, +mode: Mode = .positional, /// Tracks the true seek position in the file. To obtain the logical /// position, use `logicalPos`. pos: u64 = 0, size: ?u64 = null, size_err: ?SizeError = null, -seek_err: ?Reader.SeekError = null, +seek_err: ?SeekError = null, interface: Io.Reader, pub const Error = error{ @@ -37,15 +37,12 @@ pub const Error = error{ /// trying to read a directory file descriptor as if it were a file. NotOpenForReading, SocketUnconnected, - /// This error occurs when no global event loop is configured, - /// and reading from the file descriptor would block. + /// Non-blocking has been enabled, and reading from the file descriptor + /// would block. WouldBlock, /// In WASI, this error occurs when the file descriptor does /// not hold the required rights to read from it. AccessDenied, - /// This error occurs in Linux if the process to be read from - /// no longer exists. - ProcessNotFound, /// Unable to read file due to lock. LockViolation, } || Io.Cancelable || Io.UnexpectedError; @@ -93,9 +90,9 @@ pub const Mode = enum { pub fn initInterface(buffer: []u8) Io.Reader { return .{ .vtable = &.{ - .stream = Reader.stream, - .discard = Reader.discard, - .readVec = Reader.readVec, + .stream = stream, + .discard = discard, + .readVec = readVec, }, .buffer = buffer, .seek = 0, @@ -153,7 +150,7 @@ pub fn getSize(r: *Reader) SizeError!u64 { }; } -pub fn seekBy(r: *Reader, offset: i64) Reader.SeekError!void { +pub fn seekBy(r: *Reader, offset: i64) SeekError!void { const io = r.io; switch (r.mode) { .positional, .positional_reading => { @@ -183,7 +180,7 @@ pub fn seekBy(r: *Reader, offset: i64) Reader.SeekError!void { } /// Repositions logical read offset relative to the beginning of the file. -pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void { +pub fn seekTo(r: *Reader, offset: u64) SeekError!void { const io = r.io; switch (r.mode) { .positional, .positional_reading => { @@ -191,7 +188,7 @@ pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void { }, .streaming, .streaming_reading => { const logical_pos = logicalPos(r); - if (offset >= logical_pos) return Reader.seekBy(r, @intCast(offset - logical_pos)); + if (offset >= logical_pos) return seekBy(r, @intCast(offset - logical_pos)); if (r.seek_err) |err| return err; io.vtable.fileSeekTo(io.userdata, r.file, offset) catch |err| { r.seek_err = err; @@ -224,7 +221,7 @@ fn stream(io_reader: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.Strea return streamMode(r, w, limit, r.mode); } -pub fn streamMode(r: *Reader, w: *Io.Writer, limit: Io.Limit, mode: Reader.Mode) Io.Reader.StreamError!usize { +pub fn streamMode(r: *Reader, w: *Io.Writer, limit: Io.Limit, mode: Mode) Io.Reader.StreamError!usize { switch (mode) { .positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) { error.Unimplemented => { diff --git a/lib/std/Io/File/Writer.zig b/lib/std/Io/File/Writer.zig index 13cee3e41b..bb141f9ef6 100644 --- a/lib/std/Io/File/Writer.zig +++ b/lib/std/Io/File/Writer.zig @@ -1,53 +1,38 @@ const Writer = @This(); -const builtin = @import("builtin"); -const native_os = builtin.os.tag; -const is_windows = native_os == .windows; - const std = @import("../../std.zig"); const Io = std.Io; const File = std.Io.File; const assert = std.debug.assert; -const windows = std.os.windows; -const posix = std.posix; +io: Io, file: File, err: ?File.WriteError = null, -mode: Writer.Mode = .positional, +mode: Mode = .positional, /// Tracks the true seek position in the file. To obtain the logical /// position, add the buffer size to this value. pos: u64 = 0, -sendfile_err: ?SendfileError = null, -copy_file_range_err: ?CopyFileRangeError = null, -fcopyfile_err: ?FcopyfileError = null, -seek_err: ?Writer.SeekError = null, +write_file_err: ?WriteFileError = null, +seek_err: ?SeekError = null, interface: Io.Writer, pub const Mode = File.Reader.Mode; -pub const SendfileError = error{ - UnsupportedOperation, - SystemResources, - InputOutput, +pub const WriteFileError = error{ + /// `out_fd` is an unconnected socket, or out_fd closed its read end. BrokenPipe, + /// Descriptor is not valid or locked, or an mmap(2)-like operation is not available for in_fd. + UnsupportedOperation, + /// Nonblocking I/O has been selected but the write would block. WouldBlock, - Unexpected, -}; - -pub const CopyFileRangeError = std.os.freebsd.CopyFileRangeError || std.os.linux.wrapped.CopyFileRangeError; - -pub const FcopyfileError = error{ - OperationNotSupported, - OutOfMemory, - Unexpected, -}; + /// Unspecified error while reading from in_fd. + InputOutput, + /// Insufficient kernel memory to read from in_fd. + SystemResources, +} || Io.Cancelable || Io.UnexpectedError; pub const SeekError = Io.File.SeekError; -/// Number of slices to store on the stack, when trying to send as many byte -/// vectors through the underlying write calls as possible. -const max_buffers_len = 16; - pub fn init(file: File, buffer: []u8) Writer { return .{ .file = file, @@ -91,445 +76,122 @@ pub fn moveToReader(w: *Writer) File.Reader { pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); - const handle = w.file.handle; - const buffered = io_w.buffered(); - if (is_windows) switch (w.mode) { - .positional, .positional_reading => { - if (buffered.len != 0) { - const n = windows.WriteFile(handle, buffered, w.pos) catch |err| { - w.err = err; - return error.WriteFailed; - }; - w.pos += n; - return io_w.consume(n); - } - for (data[0 .. data.len - 1]) |buf| { - if (buf.len == 0) continue; - const n = windows.WriteFile(handle, buf, w.pos) catch |err| { - w.err = err; - return error.WriteFailed; - }; - w.pos += n; - return io_w.consume(n); - } - const pattern = data[data.len - 1]; - if (pattern.len == 0 or splat == 0) return 0; - const n = windows.WriteFile(handle, pattern, w.pos) catch |err| { - w.err = err; - return error.WriteFailed; - }; - w.pos += n; - return io_w.consume(n); - }, - .streaming, .streaming_reading => { - if (buffered.len != 0) { - const n = windows.WriteFile(handle, buffered, null) catch |err| { - w.err = err; - return error.WriteFailed; - }; - w.pos += n; - return io_w.consume(n); - } - for (data[0 .. data.len - 1]) |buf| { - if (buf.len == 0) continue; - const n = windows.WriteFile(handle, buf, null) catch |err| { - w.err = err; - return error.WriteFailed; - }; - w.pos += n; - return io_w.consume(n); - } - const pattern = data[data.len - 1]; - if (pattern.len == 0 or splat == 0) return 0; - const n = windows.WriteFile(handle, pattern, null) catch |err| { - w.err = err; - return error.WriteFailed; - }; - w.pos += n; - return io_w.consume(n); - }, - .failure => return error.WriteFailed, - }; - var iovecs: [max_buffers_len]posix.iovec_const = undefined; - var len: usize = 0; - if (buffered.len > 0) { - iovecs[len] = .{ .base = buffered.ptr, .len = buffered.len }; - len += 1; - } - for (data[0 .. data.len - 1]) |d| { - if (d.len == 0) continue; - iovecs[len] = .{ .base = d.ptr, .len = d.len }; - len += 1; - if (iovecs.len - len == 0) break; - } - const pattern = data[data.len - 1]; - if (iovecs.len - len != 0) switch (splat) { - 0 => {}, - 1 => if (pattern.len != 0) { - iovecs[len] = .{ .base = pattern.ptr, .len = pattern.len }; - len += 1; - }, - else => switch (pattern.len) { - 0 => {}, - 1 => { - const splat_buffer_candidate = io_w.buffer[io_w.end..]; - var backup_buffer: [64]u8 = undefined; - const splat_buffer = if (splat_buffer_candidate.len >= backup_buffer.len) - splat_buffer_candidate - else - &backup_buffer; - const memset_len = @min(splat_buffer.len, splat); - const buf = splat_buffer[0..memset_len]; - @memset(buf, pattern[0]); - iovecs[len] = .{ .base = buf.ptr, .len = buf.len }; - len += 1; - var remaining_splat = splat - buf.len; - while (remaining_splat > splat_buffer.len and iovecs.len - len != 0) { - assert(buf.len == splat_buffer.len); - iovecs[len] = .{ .base = splat_buffer.ptr, .len = splat_buffer.len }; - len += 1; - remaining_splat -= splat_buffer.len; - } - if (remaining_splat > 0 and iovecs.len - len != 0) { - iovecs[len] = .{ .base = splat_buffer.ptr, .len = remaining_splat }; - len += 1; - } - }, - else => for (0..splat) |_| { - iovecs[len] = .{ .base = pattern.ptr, .len = pattern.len }; - len += 1; - if (iovecs.len - len == 0) break; - }, - }, - }; - if (len == 0) return 0; switch (w.mode) { - .positional, .positional_reading => { - const n = posix.pwritev(handle, iovecs[0..len], w.pos) catch |err| switch (err) { - error.Unseekable => { - w.mode = w.mode.toStreaming(); - const pos = w.pos; - if (pos != 0) { - w.pos = 0; - w.seekTo(@intCast(pos)) catch { - w.mode = .failure; - return error.WriteFailed; - }; - } - return 0; - }, - else => |e| { - w.err = e; - return error.WriteFailed; - }, - }; - w.pos += n; - return io_w.consume(n); - }, - .streaming, .streaming_reading => { - const n = posix.writev(handle, iovecs[0..len]) catch |err| { - w.err = err; - return error.WriteFailed; - }; - w.pos += n; - return io_w.consume(n); - }, + .positional, .positional_reading => return drainPositional(w, data, splat), + .streaming, .streaming_reading => return drainStreaming(w, data, splat), .failure => return error.WriteFailed, } } -pub fn sendFile( - io_w: *Io.Writer, - file_reader: *Io.File.Reader, - limit: Io.Limit, -) Io.Writer.FileError!usize { - const reader_buffered = file_reader.interface.buffered(); - if (reader_buffered.len >= @intFromEnum(limit)) - return sendFileBuffered(io_w, file_reader, limit.slice(reader_buffered)); - const writer_buffered = io_w.buffered(); - const file_limit = @intFromEnum(limit) - reader_buffered.len; - const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); - const out_fd = w.file.handle; - const in_fd = file_reader.file.handle; - - if (file_reader.size) |size| { - if (size - file_reader.pos == 0) { - if (reader_buffered.len != 0) { - return sendFileBuffered(io_w, file_reader, reader_buffered); - } else { - return error.EndOfStream; +fn drainPositional(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { + const io = w.io; + const header = w.interface.buffered(); + const n = io.vtable.fileWritePositional(io.userdata, w.file, header, data, splat, w.pos) catch |err| switch (err) { + error.Unseekable => { + w.mode = w.mode.toStreaming(); + const pos = w.pos; + if (pos != 0) { + w.pos = 0; + w.seekTo(@intCast(pos)) catch { + w.mode = .failure; + return error.WriteFailed; + }; } - } - } - - if (native_os == .freebsd and w.mode == .streaming) sf: { - // Try using sendfile on FreeBSD. - if (w.sendfile_err != null) break :sf; - const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf; - var hdtr_data: std.c.sf_hdtr = undefined; - var headers: [2]posix.iovec_const = undefined; - var headers_i: u8 = 0; - if (writer_buffered.len != 0) { - headers[headers_i] = .{ .base = writer_buffered.ptr, .len = writer_buffered.len }; - headers_i += 1; - } - if (reader_buffered.len != 0) { - headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len }; - headers_i += 1; - } - const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: { - hdtr_data = .{ - .headers = &headers, - .hdr_cnt = headers_i, - .trailers = null, - .trl_cnt = 0, - }; - break :b &hdtr_data; - }; - var sbytes: std.c.off_t = undefined; - const nbytes: usize = @min(file_limit, std.math.maxInt(usize)); - const flags = 0; - switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, nbytes, hdtr, &sbytes, flags))) { - .SUCCESS, .INTR => {}, - .INVAL, .OPNOTSUPP, .NOTSOCK, .NOSYS => w.sendfile_err = error.UnsupportedOperation, - .BADF => if (builtin.mode == .Debug) @panic("race condition") else { - w.sendfile_err = error.Unexpected; - }, - .FAULT => if (builtin.mode == .Debug) @panic("segmentation fault") else { - w.sendfile_err = error.Unexpected; - }, - .NOTCONN => w.sendfile_err = error.BrokenPipe, - .AGAIN, .BUSY => if (sbytes == 0) { - w.sendfile_err = error.WouldBlock; - }, - .IO => w.sendfile_err = error.InputOutput, - .PIPE => w.sendfile_err = error.BrokenPipe, - .NOBUFS => w.sendfile_err = error.SystemResources, - else => |err| w.sendfile_err = posix.unexpectedErrno(err), - } - if (w.sendfile_err != null) { - // Give calling code chance to observe the error before trying - // something else. return 0; - } - if (sbytes == 0) { - file_reader.size = file_reader.pos; - return error.EndOfStream; - } - const consumed = io_w.consume(@intCast(sbytes)); - file_reader.seekBy(@intCast(consumed)) catch return error.ReadFailed; - return consumed; - } - - if (native_os.isDarwin() and w.mode == .streaming) sf: { - // Try using sendfile on macOS. - if (w.sendfile_err != null) break :sf; - const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf; - var hdtr_data: std.c.sf_hdtr = undefined; - var headers: [2]posix.iovec_const = undefined; - var headers_i: u8 = 0; - if (writer_buffered.len != 0) { - headers[headers_i] = .{ .base = writer_buffered.ptr, .len = writer_buffered.len }; - headers_i += 1; - } - if (reader_buffered.len != 0) { - headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len }; - headers_i += 1; - } - const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: { - hdtr_data = .{ - .headers = &headers, - .hdr_cnt = headers_i, - .trailers = null, - .trl_cnt = 0, - }; - break :b &hdtr_data; - }; - const max_count = std.math.maxInt(i32); // Avoid EINVAL. - var len: std.c.off_t = @min(file_limit, max_count); - const flags = 0; - switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, &len, hdtr, flags))) { - .SUCCESS, .INTR => {}, - .OPNOTSUPP, .NOTSOCK, .NOSYS => w.sendfile_err = error.UnsupportedOperation, - .BADF => if (builtin.mode == .Debug) @panic("race condition") else { - w.sendfile_err = error.Unexpected; - }, - .FAULT => if (builtin.mode == .Debug) @panic("segmentation fault") else { - w.sendfile_err = error.Unexpected; - }, - .INVAL => if (builtin.mode == .Debug) @panic("invalid API usage") else { - w.sendfile_err = error.Unexpected; - }, - .NOTCONN => w.sendfile_err = error.BrokenPipe, - .AGAIN => if (len == 0) { - w.sendfile_err = error.WouldBlock; - }, - .IO => w.sendfile_err = error.InputOutput, - .PIPE => w.sendfile_err = error.BrokenPipe, - else => |err| w.sendfile_err = posix.unexpectedErrno(err), - } - if (w.sendfile_err != null) { - // Give calling code chance to observe the error before trying - // something else. - return 0; - } - if (len == 0) { - file_reader.size = file_reader.pos; - return error.EndOfStream; - } - const consumed = io_w.consume(@bitCast(len)); - file_reader.seekBy(@intCast(consumed)) catch return error.ReadFailed; - return consumed; - } - - if (native_os == .linux and w.mode == .streaming) sf: { - // Try using sendfile on Linux. - if (w.sendfile_err != null) break :sf; - // Linux sendfile does not support headers. - if (writer_buffered.len != 0 or reader_buffered.len != 0) - return sendFileBuffered(io_w, file_reader, reader_buffered); - const max_count = 0x7ffff000; // Avoid EINVAL. - var off: std.os.linux.off_t = undefined; - const off_ptr: ?*std.os.linux.off_t, const count: usize = switch (file_reader.mode) { - .positional => o: { - const size = file_reader.getSize() catch return 0; - off = std.math.cast(std.os.linux.off_t, file_reader.pos) orelse return error.ReadFailed; - break :o .{ &off, @min(@intFromEnum(limit), size - file_reader.pos, max_count) }; - }, - .streaming => .{ null, limit.minInt(max_count) }, - .streaming_reading, .positional_reading => break :sf, - .failure => return error.ReadFailed, - }; - const n = std.os.linux.wrapped.sendfile(out_fd, in_fd, off_ptr, count) catch |err| switch (err) { - error.Unseekable => { - file_reader.mode = file_reader.mode.toStreaming(); - const pos = file_reader.pos; - if (pos != 0) { - file_reader.pos = 0; - file_reader.seekBy(@intCast(pos)) catch { - file_reader.mode = .failure; - return error.ReadFailed; - }; - } - return 0; - }, - else => |e| { - w.sendfile_err = e; - return 0; - }, - }; - if (n == 0) { - file_reader.size = file_reader.pos; - return error.EndOfStream; - } - file_reader.pos += n; - w.pos += n; - return n; - } - - const copy_file_range = switch (native_os) { - .freebsd => std.os.freebsd.copy_file_range, - .linux => std.os.linux.wrapped.copy_file_range, - else => {}, + }, + else => |e| { + w.err = e; + return error.WriteFailed; + }, }; - if (@TypeOf(copy_file_range) != void) cfr: { - if (w.copy_file_range_err != null) break :cfr; - if (writer_buffered.len != 0 or reader_buffered.len != 0) - return sendFileBuffered(io_w, file_reader, reader_buffered); - var off_in: i64 = undefined; - var off_out: i64 = undefined; - const off_in_ptr: ?*i64 = switch (file_reader.mode) { - .positional_reading, .streaming_reading => return error.Unimplemented, - .positional => p: { - off_in = @intCast(file_reader.pos); - break :p &off_in; - }, - .streaming => null, - .failure => return error.WriteFailed, - }; - const off_out_ptr: ?*i64 = switch (w.mode) { - .positional_reading, .streaming_reading => return error.Unimplemented, - .positional => p: { - off_out = @intCast(w.pos); - break :p &off_out; - }, - .streaming => null, - .failure => return error.WriteFailed, - }; - const n = copy_file_range(in_fd, off_in_ptr, out_fd, off_out_ptr, @intFromEnum(limit), 0) catch |err| { - w.copy_file_range_err = err; + w.pos += n; + return w.interface.consume(n); +} + +fn drainStreaming(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { + const io = w.io; + const header = w.interface.buffered(); + const n = io.vtable.fileWriteStreaming(io.userdata, w.file, header, data, splat) catch |err| { + w.err = err; + return error.WriteFailed; + }; + w.pos += n; + return w.interface.consume(n); +} + +pub fn sendFile(io_w: *Io.Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize { + const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); + switch (w.mode) { + .positional => return sendFilePositional(w, file_reader, limit), + .positional_reading => return error.Unimplemented, + .streaming => return sendFileStreaming(w, file_reader, limit), + .streaming_reading => return error.Unimplemented, + .failure => return error.WriteFailed, + } +} + +fn sendFilePositional(w: *Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize { + const io = w.io; + const header = w.interface.buffered(); + const n = io.vtable.fileSendFilePositional(io.userdata, w.file, header, file_reader, limit, w.pos) catch |err| switch (err) { + error.Unseekable => { + w.mode = w.mode.toStreaming(); + const pos = w.pos; + if (pos != 0) { + w.pos = 0; + w.seekTo(@intCast(pos)) catch { + w.mode = .failure; + return error.WriteFailed; + }; + } return 0; - }; - if (n == 0) { - file_reader.size = file_reader.pos; - return error.EndOfStream; - } - file_reader.pos += n; - w.pos += n; - return n; - } - - if (builtin.os.tag.isDarwin()) fcf: { - if (w.fcopyfile_err != null) break :fcf; - if (file_reader.pos != 0) break :fcf; - if (w.pos != 0) break :fcf; - if (limit != .unlimited) break :fcf; - const size = file_reader.getSize() catch break :fcf; - if (writer_buffered.len != 0 or reader_buffered.len != 0) - return sendFileBuffered(io_w, file_reader, reader_buffered); - const rc = std.c.fcopyfile(in_fd, out_fd, null, .{ .DATA = true }); - switch (posix.errno(rc)) { - .SUCCESS => {}, - .INVAL => if (builtin.mode == .Debug) @panic("invalid API usage") else { - w.fcopyfile_err = error.Unexpected; - return 0; - }, - .NOMEM => { - w.fcopyfile_err = error.OutOfMemory; - return 0; - }, - .OPNOTSUPP => { - w.fcopyfile_err = error.OperationNotSupported; - return 0; - }, - else => |err| { - w.fcopyfile_err = posix.unexpectedErrno(err); - return 0; - }, - } - file_reader.pos = size; - w.pos = size; - return size; - } - - return error.Unimplemented; + }, + error.Canceled => { + w.err = error.Canceled; + return error.WriteFailed; + }, + else => |e| { + w.write_file_err = e; + return error.WriteFailed; + }, + }; + w.pos += n; + return w.interface.consume(n); } -fn sendFileBuffered( - io_w: *Io.Writer, - file_reader: *Io.File.Reader, - reader_buffered: []const u8, -) Io.Writer.FileError!usize { - const n = try drain(io_w, &.{reader_buffered}, 1); - file_reader.seekBy(@intCast(n)) catch return error.ReadFailed; - return n; +fn sendFileStreaming(w: *Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize { + const io = w.io; + const header = w.interface.buffered(); + const n = io.vtable.fileSendFileStreaming(io.userdata, w.file, header, file_reader, limit) catch |err| switch (err) { + error.Canceled => { + w.err = error.Canceled; + return error.WriteFailed; + }, + else => |e| { + w.write_file_err = e; + return error.WriteFailed; + }, + }; + w.pos += n; + return w.interface.consume(n); } -pub fn seekTo(w: *Writer, offset: u64) (Writer.SeekError || Io.Writer.Error)!void { +pub fn seekTo(w: *Writer, offset: u64) (SeekError || Io.Writer.Error)!void { try w.interface.flush(); try seekToUnbuffered(w, offset); } /// Asserts that no data is currently buffered. -pub fn seekToUnbuffered(w: *Writer, offset: u64) Writer.SeekError!void { +pub fn seekToUnbuffered(w: *Writer, offset: u64) SeekError!void { assert(w.interface.buffered().len == 0); + const io = w.io; switch (w.mode) { .positional, .positional_reading => { w.pos = offset; }, .streaming, .streaming_reading => { if (w.seek_err) |err| return err; - posix.lseek_SET(w.file.handle, offset) catch |err| { + io.vtable.fileSeekTo(io.userdata, w.file, offset) catch |err| { w.seek_err = err; return err; }; @@ -553,7 +215,7 @@ pub fn end(w: *Writer) EndError!void { switch (w.mode) { .positional, .positional_reading, - => w.file.setEndPos(w.pos) catch |err| switch (err) { + => w.file.setLength(w.pos) catch |err| switch (err) { error.NonResizable => return, else => |e| return e, }, diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index ba5ca6caeb..d05516eae3 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -3,6 +3,7 @@ const Threaded = @This(); const builtin = @import("builtin"); const native_os = builtin.os.tag; const is_windows = native_os == .windows; +const is_darwin = native_os.isDarwin(); const windows = std.os.windows; const ws2_32 = std.os.windows.ws2_32; const is_debug = builtin.mode == .Debug; @@ -70,6 +71,10 @@ have_signal_handler: bool, old_sig_io: if (have_sig_io) posix.Sigaction else void, old_sig_pipe: if (have_sig_pipe) posix.Sigaction else void, +use_sendfile: UseSendfile = .default, +use_copy_file_range: UseCopyFileRange = .default, +use_fcopyfile: UseFcopyfile = .default, + pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum { enabled, disabled, @@ -82,6 +87,33 @@ pub const Pid = if (native_os == .linux) enum(posix.pid_t) { _, } else enum(u0) { unknown = 0 }; +pub const UseSendfile = if (have_sendfile) enum { + enabled, + disabled, + pub const default: UseSendfile = .enabled; +} else enum { + disabled, + pub const default: UseSendfile = .disabled; +}; + +pub const UseCopyFileRange = if (have_copy_file_range) enum { + enabled, + disabled, + pub const default: UseCopyFileRange = .enabled; +} else enum { + disabled, + pub const default: UseCopyFileRange = .disabled; +}; + +pub const UseFcopyfile = if (have_fcopyfile) enum { + enabled, + disabled, + pub const default: UseFcopyfile = .enabled; +} else enum { + disabled, + pub const default: UseFcopyfile = .disabled; +}; + const Thread = struct { /// The value that needs to be passed to pthread_kill or tgkill in order to /// send a signal. @@ -436,6 +468,8 @@ pub fn io(t: *Threaded) Io { .fileClose = fileClose, .fileWriteStreaming = fileWriteStreaming, .fileWritePositional = fileWritePositional, + .fileWriteFileStreaming = fileWriteFileStreaming, + .fileWriteFilePositional = fileWriteFilePositional, .fileReadStreaming = fileReadStreaming, .fileReadPositional = fileReadPositional, .fileSeekBy = fileSeekBy, @@ -556,6 +590,8 @@ pub fn ioBasic(t: *Threaded) Io { .fileClose = fileClose, .fileWriteStreaming = fileWriteStreaming, .fileWritePositional = fileWritePositional, + .fileWriteFileStreaming = fileWriteFileStreaming, + .fileWriteFilePositional = fileWriteFilePositional, .fileReadStreaming = fileReadStreaming, .fileReadPositional = fileReadPositional, .fileSeekBy = fileSeekBy, @@ -596,7 +632,7 @@ pub fn ioBasic(t: *Threaded) Io { }; } -pub const socket_flags_unsupported = native_os.isDarwin() or native_os == .haiku; +pub const socket_flags_unsupported = is_darwin or native_os == .haiku; const have_accept4 = !socket_flags_unsupported; const have_flock_open_flags = @hasField(posix.O, "EXLOCK"); const have_networking = native_os != .wasi; @@ -612,6 +648,12 @@ const have_preadv = switch (native_os) { }; const have_sig_io = posix.SIG != void and @hasField(posix.SIG, "IO"); const have_sig_pipe = posix.SIG != void and @hasField(posix.SIG, "PIPE"); +const have_sendfile = if (builtin.link_libc) @TypeOf(std.c.sendfile) != void else native_os == .linux; +const have_copy_file_range = switch (native_os) { + .linux, .freebsd => true, + else => false, +}; +const have_fcopyfile = is_darwin; const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat; const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat; @@ -619,6 +661,18 @@ const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.syste const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek; const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv; const ftruncate_sym = if (posix.lfs64_abi) posix.system.ftruncate64 else posix.system.ftruncate; +const pwritev_sym = if (posix.lfs64_abi) posix.system.pwritev64 else posix.system.pwritev; +const sendfile_sym = if (posix.lfs64_abi) posix.system.sendfile64 else posix.system.sendfile; +const linux_copy_file_range_use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) .{ + .major = 34, + .minor = 0, + .patch = 0, +} else .{ + .major = 2, + .minor = 27, + .patch = 0, +}); +const linux_copy_file_range_sys = if (linux_copy_file_range_use_c) std.c else std.os.linux; /// Trailing data: /// 1. context @@ -5715,27 +5769,828 @@ fn openSelfExe(userdata: ?*anyopaque, flags: Io.File.OpenFlags) Io.File.OpenSelf fn fileWritePositional( userdata: ?*anyopaque, file: Io.File, - buffer: [][]const u8, + header: []const u8, + data: []const []const u8, + splat: usize, offset: u64, ) Io.File.WritePositionalError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); - _ = t; + const current_thread = Thread.getCurrent(t); + + if (is_windows) @panic("TODO"); + + var iovecs: [max_iovecs_len]posix.iovec_const = undefined; + var iovlen: iovlen_t = 0; + addBuf(&iovecs, &iovlen, header); + for (data[0 .. data.len - 1]) |bytes| addBuf(&iovecs, &iovlen, bytes); + const pattern = data[data.len - 1]; + if (iovecs.len - iovlen != 0) switch (splat) { + 0 => {}, + 1 => addBuf(&iovecs, &iovlen, pattern), + else => switch (pattern.len) { + 0 => {}, + 1 => { + var backup_buffer: [splat_buffer_size]u8 = undefined; + const splat_buffer = &backup_buffer; + const memset_len = @min(splat_buffer.len, splat); + const buf = splat_buffer[0..memset_len]; + @memset(buf, pattern[0]); + addBuf(&iovecs, &iovlen, buf); + var remaining_splat = splat - buf.len; + while (remaining_splat > splat_buffer.len and iovecs.len - iovlen != 0) { + assert(buf.len == splat_buffer.len); + addBuf(&iovecs, &iovlen, splat_buffer); + remaining_splat -= splat_buffer.len; + } + addBuf(&iovecs, &iovlen, splat_buffer[0..remaining_splat]); + }, + else => for (0..@min(splat, iovecs.len - iovlen)) |_| { + addBuf(&iovecs, &iovlen, pattern); + }, + }, + }; + + if (native_os == .wasi and !builtin.link_libc) { + var n_written: usize = undefined; + try current_thread.beginSyscall(); + while (true) { + switch (std.os.wasi.fd_pwrite(file.handle, &iovecs, iovlen, offset, &n_written)) { + .SUCCESS => { + current_thread.endSyscall(); + return n_written; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .INVAL => |err| return errnoBug(err), + .FAULT => |err| return errnoBug(err), + .AGAIN => |err| return errnoBug(err), + .BADF => return error.NotOpenForWriting, // can be a race condition. + .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called. + .DQUOT => return error.DiskQuota, + .FBIG => return error.FileTooBig, + .IO => return error.InputOutput, + .NOSPC => return error.NoSpaceLeft, + .PERM => return error.PermissionDenied, + .PIPE => return error.BrokenPipe, + .NOTCAPABLE => return error.AccessDenied, + .NXIO => return error.Unseekable, + .SPIPE => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } + } + + try current_thread.beginSyscall(); while (true) { - _ = file; - _ = buffer; - _ = offset; - @panic("TODO implement fileWritePositional"); + const rc = pwritev_sym(file.handle, &iovecs, iovlen, @bitCast(offset)); + switch (posix.errno(rc)) { + .SUCCESS => { + current_thread.endSyscall(); + return @intCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .INVAL => return error.InvalidArgument, + .FAULT => |err| return errnoBug(err), + .AGAIN => return error.WouldBlock, + .BADF => return error.NotOpenForWriting, // Usually a race condition. + .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called. + .DQUOT => return error.DiskQuota, + .FBIG => return error.FileTooBig, + .IO => return error.InputOutput, + .NOSPC => return error.NoSpaceLeft, + .PERM => return error.PermissionDenied, + .PIPE => return error.BrokenPipe, + .CONNRESET => return error.ConnectionResetByPeer, + .BUSY => return error.DeviceBusy, + .NXIO => return error.Unseekable, + .SPIPE => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + else => |err| return posix.unexpectedErrno(err), + } + }, + } } } -fn fileWriteStreaming(userdata: ?*anyopaque, file: Io.File, buffer: [][]const u8) Io.File.WriteStreamingError!usize { +fn fileWriteStreaming( + userdata: ?*anyopaque, + file: Io.File, + header: []const u8, + data: []const []const u8, + splat: usize, +) Io.File.WriteStreamingError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); - _ = t; - while (true) { - _ = file; - _ = buffer; - @panic("TODO implement fileWriteStreaming"); + const current_thread = Thread.getCurrent(t); + + if (is_windows) @panic("TODO"); + + var iovecs: [max_iovecs_len]posix.iovec_const = undefined; + var iovlen: iovlen_t = 0; + addBuf(&iovecs, &iovlen, header); + for (data[0 .. data.len - 1]) |bytes| addBuf(&iovecs, &iovlen, bytes); + const pattern = data[data.len - 1]; + if (iovecs.len - iovlen != 0) switch (splat) { + 0 => {}, + 1 => addBuf(&iovecs, &iovlen, pattern), + else => switch (pattern.len) { + 0 => {}, + 1 => { + var backup_buffer: [splat_buffer_size]u8 = undefined; + const splat_buffer = &backup_buffer; + const memset_len = @min(splat_buffer.len, splat); + const buf = splat_buffer[0..memset_len]; + @memset(buf, pattern[0]); + addBuf(&iovecs, &iovlen, buf); + var remaining_splat = splat - buf.len; + while (remaining_splat > splat_buffer.len and iovecs.len - iovlen != 0) { + assert(buf.len == splat_buffer.len); + addBuf(&iovecs, &iovlen, splat_buffer); + remaining_splat -= splat_buffer.len; + } + addBuf(&iovecs, &iovlen, splat_buffer[0..remaining_splat]); + }, + else => for (0..@min(splat, iovecs.len - iovlen)) |_| { + addBuf(&iovecs, &iovlen, pattern); + }, + }, + }; + + if (native_os == .wasi and !builtin.link_libc) { + var n_written: usize = undefined; + try current_thread.beginSyscall(); + while (true) { + switch (std.os.wasi.fd_write(file.handle, &iovecs, iovlen, &n_written)) { + .SUCCESS => { + current_thread.endSyscall(); + return n_written; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .INVAL => |err| return errnoBug(err), + .FAULT => |err| return errnoBug(err), + .AGAIN => |err| return errnoBug(err), + .BADF => return error.NotOpenForWriting, // can be a race condition. + .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called. + .DQUOT => return error.DiskQuota, + .FBIG => return error.FileTooBig, + .IO => return error.InputOutput, + .NOSPC => return error.NoSpaceLeft, + .PERM => return error.PermissionDenied, + .PIPE => return error.BrokenPipe, + .NOTCAPABLE => return error.AccessDenied, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } } + + try current_thread.beginSyscall(); + while (true) { + const rc = posix.system.writev(file.handle, &iovecs, iovlen); + switch (posix.errno(rc)) { + .SUCCESS => { + current_thread.endSyscall(); + return @intCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .INVAL => return error.InvalidArgument, + .FAULT => |err| return errnoBug(err), + .SRCH => return error.ProcessNotFound, + .AGAIN => return error.WouldBlock, + .BADF => return error.NotOpenForWriting, // Can be a race condition. + .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called. + .DQUOT => return error.DiskQuota, + .FBIG => return error.FileTooBig, + .IO => return error.InputOutput, + .NOSPC => return error.NoSpaceLeft, + .PERM => return error.PermissionDenied, + .PIPE => return error.BrokenPipe, + .CONNRESET => return error.ConnectionResetByPeer, + .BUSY => return error.DeviceBusy, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } +} + +fn fileWriteFileStreaming( + userdata: ?*anyopaque, + file: Io.File, + header: []const u8, + file_reader: *Io.File.Reader, + limit: Io.Limit, +) Io.File.WriteFileStreamingError!usize { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const reader_buffered = file_reader.interface.buffered(); + if (reader_buffered.len >= @intFromEnum(limit)) { + const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1); + file_reader.seekBy(n -| header.len) catch return error.ReadFailed; + return n; + } + const file_limit = @intFromEnum(limit) - reader_buffered.len; + const out_fd = file.handle; + const in_fd = file_reader.file.handle; + + if (file_reader.size) |size| { + if (size - file_reader.pos == 0) { + if (reader_buffered.len != 0) { + const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1); + file_reader.seekBy(n -| header.len) catch return error.ReadFailed; + return n; + } else { + return error.EndOfStream; + } + } + } + + if (native_os == .freebsd) sf: { + // Try using sendfile on FreeBSD. + if (@atomicLoad(UseSendfile, &t.use_sendfile, .monotonic) == .disabled) break :sf; + const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf; + var hdtr_data: std.c.sf_hdtr = undefined; + var headers: [2]posix.iovec_const = undefined; + var headers_i: u8 = 0; + if (header.len != 0) { + headers[headers_i] = .{ .base = header.ptr, .len = header.len }; + headers_i += 1; + } + if (reader_buffered.len != 0) { + headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len }; + headers_i += 1; + } + const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: { + hdtr_data = .{ + .headers = &headers, + .hdr_cnt = headers_i, + .trailers = null, + .trl_cnt = 0, + }; + break :b &hdtr_data; + }; + var sbytes: std.c.off_t = 0; + const nbytes: usize = @min(file_limit, std.math.maxInt(usize)); + const flags = 0; + + const current_thread = Thread.getCurrent(t); + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, nbytes, hdtr, &sbytes, flags))) { + .SUCCESS => { + current_thread.endSyscall(); + break; + }, + .INVAL, .OPNOTSUPP, .NOTSOCK, .NOSYS => { + // Give calling code chance to observe before trying + // something else. + current_thread.endSyscall(); + @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic); + return 0; + }, + .INTR, .BUSY => { + if (sbytes == 0) { + try current_thread.checkCancel(); + continue; + } else { + // Even if we are being canceled, there have been side + // effects, so it is better to report those side + // effects to the caller. + current_thread.endSyscall(); + break; + } + }, + .AGAIN => { + current_thread.endSyscall(); + if (sbytes == 0) return error.WouldBlock; + break; + }, + else => |e| { + current_thread.endSyscall(); + assert(error.Unexpected == switch (e) { + .NOTCONN => return error.BrokenPipe, + .IO => return error.InputOutput, + .PIPE => return error.BrokenPipe, + .NOBUFS => return error.SystemResources, + .BADF => |err| errnoBug(err), + .FAULT => |err| errnoBug(err), + else => |err| posix.unexpectedErrno(err), + }); + // Give calling code chance to observe the error before trying + // something else. + @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic); + return 0; + }, + } + } + if (sbytes == 0) { + file_reader.size = file_reader.pos; + return error.EndOfStream; + } + const ubytes: usize = @intCast(sbytes); + file_reader.seekBy(ubytes -| header.len) catch return error.ReadFailed; + return ubytes; + } + + if (is_darwin) sf: { + // Try using sendfile on macOS. + if (@atomicLoad(UseSendfile, &t.use_sendfile, .monotonic) == .disabled) break :sf; + const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf; + var hdtr_data: std.c.sf_hdtr = undefined; + var headers: [2]posix.iovec_const = undefined; + var headers_i: u8 = 0; + if (header.len != 0) { + headers[headers_i] = .{ .base = header.ptr, .len = header.len }; + headers_i += 1; + } + if (reader_buffered.len != 0) { + headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len }; + headers_i += 1; + } + const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: { + hdtr_data = .{ + .headers = &headers, + .hdr_cnt = headers_i, + .trailers = null, + .trl_cnt = 0, + }; + break :b &hdtr_data; + }; + const max_count = std.math.maxInt(i32); // Avoid EINVAL. + var len: std.c.off_t = @min(file_limit, max_count); + const flags = 0; + const current_thread = Thread.getCurrent(t); + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, &len, hdtr, flags))) { + .SUCCESS => { + current_thread.endSyscall(); + break; + }, + .OPNOTSUPP, .NOTSOCK, .NOSYS => { + // Give calling code chance to observe before trying + // something else. + current_thread.endSyscall(); + @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic); + return 0; + }, + .INTR => { + if (len == 0) { + try current_thread.checkCancel(); + continue; + } else { + // Even if we are being canceled, there have been side + // effects, so it is better to report those side + // effects to the caller. + current_thread.endSyscall(); + break; + } + }, + .AGAIN => { + current_thread.endSyscall(); + if (len == 0) return error.WouldBlock; + break; + }, + else => |e| { + current_thread.endSyscall(); + assert(error.Unexpected == switch (e) { + .NOTCONN => return error.BrokenPipe, + .IO => return error.InputOutput, + .PIPE => return error.BrokenPipe, + .BADF => |err| errnoBug(err), + .FAULT => |err| errnoBug(err), + .INVAL => |err| errnoBug(err), + else => |err| posix.unexpectedErrno(err), + }); + // Give calling code chance to observe the error before trying + // something else. + @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic); + return 0; + }, + } + } + if (len == 0) { + file_reader.size = file_reader.pos; + return error.EndOfStream; + } + const u_len: usize = @bitCast(len); + file_reader.seekBy(u_len -| header.len) catch return error.ReadFailed; + return u_len; + } + + if (native_os == .linux) sf: { + // Try using sendfile on Linux. + if (@atomicLoad(UseSendfile, &t.use_sendfile, .monotonic) == .disabled) break :sf; + // Linux sendfile does not support headers. + if (header.len != 0 or reader_buffered.len != 0) { + const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1); + file_reader.seekBy(n -| header.len) catch return error.ReadFailed; + return n; + } + const max_count = 0x7ffff000; // Avoid EINVAL. + var off: std.os.linux.off_t = undefined; + const off_ptr: ?*std.os.linux.off_t, const count: usize = switch (file_reader.mode) { + .positional => o: { + const size = file_reader.getSize() catch return 0; + off = std.math.cast(std.os.linux.off_t, file_reader.pos) orelse return error.ReadFailed; + break :o .{ &off, @min(@intFromEnum(limit), size - file_reader.pos, max_count) }; + }, + .streaming => .{ null, limit.minInt(max_count) }, + .streaming_reading, .positional_reading => break :sf, + .failure => return error.ReadFailed, + }; + const current_thread = Thread.getCurrent(t); + try current_thread.beginSyscall(); + const n: usize = while (true) { + const rc = sendfile_sym(out_fd, in_fd, off_ptr, count); + switch (posix.errno(rc)) { + .SUCCESS => { + current_thread.endSyscall(); + break @intCast(rc); + }, + .NOSYS, .INVAL => { + // Give calling code chance to observe before trying + // something else. + current_thread.endSyscall(); + @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic); + return 0; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + assert(error.Unexpected == switch (e) { + .NOTCONN => return error.BrokenPipe, // `out_fd` is an unconnected socket + .AGAIN => return error.WouldBlock, + .IO => return error.InputOutput, + .PIPE => return error.BrokenPipe, + .NOMEM => return error.SystemResources, + .NXIO, .SPIPE => { + file_reader.mode = file_reader.mode.toStreaming(); + const pos = file_reader.pos; + if (pos != 0) { + file_reader.pos = 0; + file_reader.seekBy(@intCast(pos)) catch { + file_reader.mode = .failure; + return error.ReadFailed; + }; + } + return 0; + }, + .BADF => |err| errnoBug(err), // Always a race condition. + .FAULT => |err| errnoBug(err), // Segmentation fault. + .OVERFLOW => |err| errnoBug(err), // We avoid passing too large of a `count`. + else => |err| posix.unexpectedErrno(err), + }); + // Give calling code chance to observe the error before trying + // something else. + @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic); + return 0; + }, + } + }; + if (n == 0) { + file_reader.size = file_reader.pos; + return error.EndOfStream; + } + file_reader.pos += n; + return n; + } + + if (have_copy_file_range) cfr: { + if (@atomicLoad(UseCopyFileRange, &t.use_copy_file_range, .monotonic) == .disabled) break :cfr; + if (header.len != 0 or reader_buffered.len != 0) { + const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1); + file_reader.seekBy(n -| header.len) catch return error.ReadFailed; + return n; + } + var off_in: i64 = undefined; + const off_in_ptr: ?*i64 = switch (file_reader.mode) { + .positional_reading, .streaming_reading => return error.Unimplemented, + .positional => p: { + off_in = @intCast(file_reader.pos); + break :p &off_in; + }, + .streaming => null, + .failure => return error.WriteFailed, + }; + const current_thread = Thread.getCurrent(t); + const n: usize = switch (native_os) { + .linux => n: { + try current_thread.beginSyscall(); + while (true) { + const rc = linux_copy_file_range_sys.copy_file_range(in_fd, off_in_ptr, out_fd, null, @intFromEnum(limit), 0); + switch (linux_copy_file_range_sys.errno(rc)) { + .SUCCESS => { + current_thread.endSyscall(); + break :n @intCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + .OPNOTSUPP, .INVAL, .NOSYS => { + // Give calling code chance to observe before trying + // something else. + current_thread.endSyscall(); + @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); + return 0; + }, + else => |e| { + current_thread.endSyscall(); + assert(error.Unexpected == switch (e) { + .FBIG => return error.FileTooBig, + .IO => return error.InputOutput, + .ISDIR => return error.IsDir, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .OVERFLOW => return error.Overflow, + .PERM => return error.PermissionDenied, + .TXTBSY => return error.SwapFile, + .XDEV => return error.NotSameFileSystem, + .BADF => |err| errnoBug(err), + else => |err| posix.unexpectedErrno(err), + }); + @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); + return 0; + }, + } + } + }, + .freebsd => n: { + try current_thread.beginSyscall(); + while (true) { + const rc = std.c.copy_file_range(in_fd, off_in_ptr, out_fd, null, @intFromEnum(limit), 0); + switch (std.c.errno(rc)) { + .SUCCESS => { + current_thread.endSyscall(); + break :n @intCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .OPNOTSUPP, .INVAL, .NOSYS => { + // Give calling code chance to observe before trying + // something else. + current_thread.endSyscall(); + @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); + return 0; + }, + else => |e| { + current_thread.endSyscall(); + assert(error.Unexpected == switch (e) { + .FBIG => return error.FileTooBig, + .IO => return error.InputOutput, + .INTEGRITY => return error.CorruptedData, + .ISDIR => return error.IsDir, + .NOSPC => return error.NoSpaceLeft, + .BADF => |err| errnoBug(err), + else => |err| posix.unexpectedErrno(err), + }); + @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); + return 0; + }, + } + } + }, + else => comptime unreachable, + }; + if (n == 0) { + file_reader.size = file_reader.pos; + return error.EndOfStream; + } + file_reader.pos += n; + return n; + } + + return error.Unimplemented; +} + +fn fileWriteFilePositional( + userdata: ?*anyopaque, + file: Io.File, + header: []const u8, + file_reader: *Io.File.Reader, + limit: Io.Limit, + offset: u64, +) Io.File.WriteFilePositionalError!usize { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const reader_buffered = file_reader.interface.buffered(); + if (reader_buffered.len >= @intFromEnum(limit)) { + const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset); + file_reader.seekBy(n -| header.len) catch return error.ReadFailed; + return n; + } + const out_fd = file.handle; + const in_fd = file_reader.file.handle; + + if (file_reader.size) |size| { + if (size - file_reader.pos == 0) { + if (reader_buffered.len != 0) { + const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset); + file_reader.seekBy(n -| header.len) catch return error.ReadFailed; + return n; + } else { + return error.EndOfStream; + } + } + } + + if (have_copy_file_range) cfr: { + if (@atomicLoad(UseCopyFileRange, &t.use_copy_file_range, .monotonic) == .disabled) break :cfr; + if (header.len != 0 or reader_buffered.len != 0) { + const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset); + file_reader.seekBy(n -| header.len) catch return error.ReadFailed; + return n; + } + var off_in: i64 = undefined; + const off_in_ptr: ?*i64 = switch (file_reader.mode) { + .positional_reading, .streaming_reading => return error.Unimplemented, + .positional => p: { + off_in = @intCast(file_reader.pos); + break :p &off_in; + }, + .streaming => null, + .failure => return error.WriteFailed, + }; + var off_out: i64 = @intCast(offset); + const current_thread = Thread.getCurrent(t); + const n: usize = switch (native_os) { + .linux => n: { + try current_thread.beginSyscall(); + while (true) { + const rc = linux_copy_file_range_sys.copy_file_range(in_fd, off_in_ptr, out_fd, &off_out, @intFromEnum(limit), 0); + switch (linux_copy_file_range_sys.errno(rc)) { + .SUCCESS => { + current_thread.endSyscall(); + break :n @intCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + .OPNOTSUPP, .INVAL, .NOSYS => { + // Give calling code chance to observe before trying + // something else. + current_thread.endSyscall(); + @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); + return 0; + }, + else => |e| { + current_thread.endSyscall(); + assert(error.Unexpected == switch (e) { + .FBIG => return error.FileTooBig, + .IO => return error.InputOutput, + .ISDIR => return error.IsDir, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .OVERFLOW => return error.Unseekable, + .NXIO => return error.Unseekable, + .SPIPE => return error.Unseekable, + .PERM => return error.PermissionDenied, + .TXTBSY => return error.SwapFile, + .XDEV => return error.NotSameFileSystem, + .BADF => |err| errnoBug(err), + else => |err| posix.unexpectedErrno(err), + }); + @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); + return 0; + }, + } + } + }, + .freebsd => n: { + try current_thread.beginSyscall(); + while (true) { + const rc = std.c.copy_file_range(in_fd, off_in_ptr, out_fd, &off_out, @intFromEnum(limit), 0); + switch (std.c.errno(rc)) { + .SUCCESS => { + current_thread.endSyscall(); + break :n @intCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .OPNOTSUPP, .INVAL, .NOSYS => { + // Give calling code chance to observe before trying + // something else. + current_thread.endSyscall(); + @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); + return 0; + }, + else => |e| { + current_thread.endSyscall(); + assert(error.Unexpected == switch (e) { + .FBIG => return error.FileTooBig, + .IO => return error.InputOutput, + .INTEGRITY => return error.CorruptedData, + .ISDIR => return error.IsDir, + .NOSPC => return error.NoSpaceLeft, + .OVERFLOW => return error.Unseekable, + .NXIO => return error.Unseekable, + .SPIPE => return error.Unseekable, + .BADF => |err| errnoBug(err), + else => |err| posix.unexpectedErrno(err), + }); + @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); + return 0; + }, + } + } + }, + else => comptime unreachable, + }; + if (n == 0) { + file_reader.size = file_reader.pos; + return error.EndOfStream; + } + file_reader.pos += n; + return n; + } + + if (is_darwin) fcf: { + if (@atomicLoad(UseFcopyfile, &t.use_fcopyfile, .monotonic) == .disabled) break :fcf; + if (file_reader.pos != 0) break :fcf; + if (offset != 0) break :fcf; + if (limit != .unlimited) break :fcf; + const size = file_reader.getSize() catch break :fcf; + if (header.len != 0 or reader_buffered.len != 0) { + const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset); + file_reader.seekBy(n -| header.len) catch return error.ReadFailed; + return n; + } + const current_thread = Thread.getCurrent(t); + try current_thread.beginSyscall(); + while (true) { + const rc = std.c.fcopyfile(in_fd, out_fd, null, .{ .DATA = true }); + switch (posix.errno(rc)) { + .SUCCESS => { + current_thread.endSyscall(); + break; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .OPNOTSUPP => { + // Give calling code chance to observe before trying + // something else. + current_thread.endSyscall(); + @atomicStore(UseFcopyfile, &t.use_fcopyfile, .disabled, .monotonic); + return 0; + }, + else => |e| { + current_thread.endSyscall(); + assert(error.Unexpected == switch (e) { + .NOMEM => return error.SystemResources, + .INVAL => |err| posix.errnoBug(err), + else => |err| posix.unexpectedErrno(err), + }); + return 0; + }, + } + } + file_reader.pos = size; + return size; + } + + return error.Unimplemented; } fn nowPosix(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp { @@ -7666,6 +8521,7 @@ fn netWritePosix( }, }; const flags = posix.MSG.NOSIGNAL; + try current_thread.beginSyscall(); while (true) { const rc = posix.system.sendmsg(fd, &msg, flags); @@ -7829,7 +8685,11 @@ fn netWriteUnavailable( return error.NetworkDown; } -fn addBuf(v: []posix.iovec_const, i: *@FieldType(posix.msghdr_const, "iovlen"), bytes: []const u8) void { +/// This is either usize or u32. Since, either is fine, let's use the same +/// `addBuf` function for both writing to a file and sending network messages. +const iovlen_t = @FieldType(posix.msghdr_const, "iovlen"); + +fn addBuf(v: []posix.iovec_const, i: *iovlen_t, bytes: []const u8) void { // OS checks ptr addr before length so zero length vectors must be omitted. if (bytes.len == 0) return; if (v.len - i.* == 0) return; @@ -8152,7 +9012,7 @@ fn netLookupFallible( // TODO use dnsres_getaddrinfo } - if (native_os.isDarwin()) { + if (is_darwin) { // TODO use CFHostStartInfoResolution / CFHostCancelInfoResolution } diff --git a/lib/std/os.zig b/lib/std/os.zig index fdd5ab2480..77a3833c2b 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -30,7 +30,6 @@ pub const uefi = @import("os/uefi.zig"); pub const wasi = @import("os/wasi.zig"); pub const emscripten = @import("os/emscripten.zig"); pub const windows = @import("os/windows.zig"); -pub const freebsd = @import("os/freebsd.zig"); test { _ = linux; diff --git a/lib/std/os/freebsd.zig b/lib/std/os/freebsd.zig deleted file mode 100644 index 2d082bf0cd..0000000000 --- a/lib/std/os/freebsd.zig +++ /dev/null @@ -1,50 +0,0 @@ -const std = @import("../std.zig"); -const fd_t = std.c.fd_t; -const off_t = std.c.off_t; -const unexpectedErrno = std.posix.unexpectedErrno; -const errno = std.posix.errno; -const builtin = @import("builtin"); - -pub const CopyFileRangeError = std.posix.UnexpectedError || error{ - /// If infd is not open for reading or outfd is not open for writing, or - /// opened for writing with O_APPEND, or if infd and outfd refer to the - /// same file. - BadFileFlags, - /// If the copy exceeds the process's file size limit or the maximum - /// file size for the file system outfd re- sides on. - FileTooBig, - /// A signal interrupted the system call before it could be completed. - /// This may happen for files on some NFS mounts. When this happens, - /// the values pointed to by inoffp and outoffp are reset to the - /// initial values for the system call. - Interrupted, - /// One of: - /// * infd and outfd refer to the same file and the byte ranges overlap. - /// * The flags argument is not zero. - /// * Either infd or outfd refers to a file object that is not a regular file. - InvalidArguments, - /// An I/O error occurred while reading/writing the files. - InputOutput, - /// Corrupted data was detected while reading from a file system. - CorruptedData, - /// Either infd or outfd refers to a directory. - IsDir, - /// File system that stores outfd is full. - NoSpaceLeft, -}; - -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 { - const rc = std.c.copy_file_range(fd_in, off_in, fd_out, off_out, len, flags); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .BADF => return error.BadFileFlags, - .FBIG => return error.FileTooBig, - .INTR => return error.Interrupted, - .INVAL => return error.InvalidArguments, - .IO => return error.InputOutput, - .INTEGRITY => return error.CorruptedData, - .ISDIR => return error.IsDir, - .NOSPC => return error.NoSpaceLeft, - else => |err| return unexpectedErrno(err), - } -} diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index b80d7c606f..76a684a3e7 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -9855,125 +9855,3 @@ pub const cmsghdr = extern struct { level: i32, type: i32, }; - -/// The syscalls, but with Zig error sets, going through libc if linking libc, -/// and with some footguns eliminated. -pub const wrapped = struct { - pub const lfs64_abi = builtin.link_libc and (builtin.abi.isGnu() or builtin.abi.isAndroid()); - const system = if (builtin.link_libc) std.c else std.os.linux; - - pub const SendfileError = std.posix.UnexpectedError || error{ - /// `out_fd` is an unconnected socket, or out_fd closed its read end. - BrokenPipe, - /// Descriptor is not valid or locked, or an mmap(2)-like operation is not available for in_fd. - UnsupportedOperation, - /// Nonblocking I/O has been selected but the write would block. - WouldBlock, - /// Unspecified error while reading from in_fd. - InputOutput, - /// Insufficient kernel memory to read from in_fd. - SystemResources, - /// `offset` is not `null` but the input file is not seekable. - Unseekable, - }; - - pub fn sendfile( - out_fd: fd_t, - in_fd: fd_t, - in_offset: ?*off_t, - in_len: usize, - ) SendfileError!usize { - const adjusted_len = @min(in_len, 0x7ffff000); // Prevents EOVERFLOW. - const sendfileSymbol = if (lfs64_abi) system.sendfile64 else system.sendfile; - const rc = sendfileSymbol(out_fd, in_fd, in_offset, adjusted_len); - switch (system.errno(rc)) { - .SUCCESS => return @intCast(rc), - .BADF => return invalidApiUsage(), // Always a race condition. - .FAULT => return invalidApiUsage(), // Segmentation fault. - .OVERFLOW => return unexpectedErrno(.OVERFLOW), // We avoid passing too large of a `count`. - .NOTCONN => return error.BrokenPipe, // `out_fd` is an unconnected socket - .INVAL => return error.UnsupportedOperation, - .AGAIN => return error.WouldBlock, - .IO => return error.InputOutput, - .PIPE => return error.BrokenPipe, - .NOMEM => return error.SystemResources, - .NXIO => return error.Unseekable, - .SPIPE => return error.Unseekable, - else => |err| return unexpectedErrno(err), - } - } - - pub const CopyFileRangeError = std.posix.UnexpectedError || error{ - /// One of: - /// * One or more file descriptors are not valid. - /// * fd_in is not open for reading; or fd_out is not open for writing. - /// * The O_APPEND flag is set for the open file description referred - /// to by the file descriptor fd_out. - BadFileFlags, - /// One of: - /// * An attempt was made to write at a position past the maximum file - /// offset the kernel supports. - /// * An attempt was made to write a range that exceeds the allowed - /// maximum file size. The maximum file size differs between - /// filesystem implementations and can be different from the maximum - /// allowed file offset. - /// * An attempt was made to write beyond the process's file size - /// resource limit. This may also result in the process receiving a - /// SIGXFSZ signal. - FileTooBig, - /// One of: - /// * either fd_in or fd_out is not a regular file - /// * flags argument is not zero - /// * fd_in and fd_out refer to the same file and the source and target ranges overlap. - InvalidArguments, - /// A low-level I/O error occurred while copying. - InputOutput, - /// Either fd_in or fd_out refers to a directory. - IsDir, - OutOfMemory, - /// There is not enough space on the target filesystem to complete the copy. - NoSpaceLeft, - /// (since Linux 5.19) the filesystem does not support this operation. - OperationNotSupported, - /// The requested source or destination range is too large to represent - /// in the specified data types. - Overflow, - /// fd_out refers to an immutable file. - PermissionDenied, - /// Either fd_in or fd_out refers to an active swap file. - SwapFile, - /// The files referred to by fd_in and fd_out are not on the same - /// filesystem, and the source and target filesystems are not of the - /// same type, or do not support cross-filesystem copy. - NotSameFileSystem, - }; - - 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 { - const use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) .{ .major = 34, .minor = 0, .patch = 0 } else .{ .major = 2, .minor = 27, .patch = 0 }); - const sys = if (use_c) std.c else std.os.linux; - const rc = sys.copy_file_range(fd_in, off_in, fd_out, off_out, len, flags); - switch (sys.errno(rc)) { - .SUCCESS => return @intCast(rc), - .BADF => return error.BadFileFlags, - .FBIG => return error.FileTooBig, - .INVAL => return error.InvalidArguments, - .IO => return error.InputOutput, - .ISDIR => return error.IsDir, - .NOMEM => return error.OutOfMemory, - .NOSPC => return error.NoSpaceLeft, - .OPNOTSUPP => return error.OperationNotSupported, - .OVERFLOW => return error.Overflow, - .PERM => return error.PermissionDenied, - .TXTBSY => return error.SwapFile, - .XDEV => return error.NotSameFileSystem, - else => |err| return unexpectedErrno(err), - } - } - - const unexpectedErrno = std.posix.unexpectedErrno; - - fn invalidApiUsage() error{Unexpected} { - if (builtin.mode == .Debug) @panic("invalid API usage"); - return error.Unexpected; - } -}; diff --git a/lib/std/os/linux/test.zig b/lib/std/os/linux/test.zig index f5533a54bd..bed3309157 100644 --- a/lib/std/os/linux/test.zig +++ b/lib/std/os/linux/test.zig @@ -405,14 +405,6 @@ test "futex2_requeue" { try expectEqual(0, rc); } -test "copy_file_range error" { - const fds = try std.posix.pipe(); - defer std.posix.close(fds[0]); - defer std.posix.close(fds[1]); - - try std.testing.expectError(error.InvalidArguments, linux.wrapped.copy_file_range(fds[0], null, fds[1], null, 1, 0)); -} - test { _ = linux.IoUring; } diff --git a/lib/std/posix.zig b/lib/std/posix.zig index bbb0da0ec8..4641c52cb3 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -1140,80 +1140,6 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize { } } -/// Write multiple buffers to a file descriptor. -/// Retries when interrupted by a signal. -/// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero. -/// -/// Note that a successful write() may transfer fewer bytes than supplied. Such partial writes can -/// occur for various reasons; for example, because there was insufficient space on the disk -/// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or -/// similar was interrupted by a signal handler after it had transferred some, but before it had -/// transferred all of the requested bytes. In the event of a partial write, the caller can make -/// another write() call to transfer the remaining bytes. The subsequent call will either -/// transfer further bytes or may result in an error (e.g., if the disk is now full). -/// -/// For POSIX systems, if `fd` is opened in non blocking mode, the function will -/// return error.WouldBlock when EAGAIN is received. -/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are -/// used to perform the I/O. `error.WouldBlock` is not possible on Windows. -/// -/// If `iov.len` is larger than `IOV_MAX`, a partial write will occur. -/// -/// This function assumes that all vectors, including zero-length vectors, have -/// a pointer within the address space of the application. -pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize { - if (native_os == .windows) { - // TODO improve this to use WriteFileScatter - if (iov.len == 0) return 0; - const first = iov[0]; - return write(fd, first.base[0..first.len]); - } - if (native_os == .wasi and !builtin.link_libc) { - var nwritten: usize = undefined; - switch (wasi.fd_write(fd, iov.ptr, iov.len, &nwritten)) { - .SUCCESS => return nwritten, - .INTR => unreachable, - .INVAL => unreachable, - .FAULT => unreachable, - .AGAIN => unreachable, - .BADF => return error.NotOpenForWriting, // can be a race condition. - .DESTADDRREQ => unreachable, // `connect` was never called. - .DQUOT => return error.DiskQuota, - .FBIG => return error.FileTooBig, - .IO => return error.InputOutput, - .NOSPC => return error.NoSpaceLeft, - .PERM => return error.PermissionDenied, - .PIPE => return error.BrokenPipe, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } - } - - while (true) { - const rc = system.writev(fd, iov.ptr, @min(iov.len, IOV_MAX)); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .INTR => continue, - .INVAL => return error.InvalidArgument, - .FAULT => unreachable, - .SRCH => return error.ProcessNotFound, - .AGAIN => return error.WouldBlock, - .BADF => return error.NotOpenForWriting, // Can be a race condition. - .DESTADDRREQ => unreachable, // `connect` was never called. - .DQUOT => return error.DiskQuota, - .FBIG => return error.FileTooBig, - .IO => return error.InputOutput, - .NOSPC => return error.NoSpaceLeft, - .PERM => return error.PermissionDenied, - .PIPE => return error.BrokenPipe, - .CONNRESET => return error.ConnectionResetByPeer, - .BUSY => return error.DeviceBusy, - .CANCELED => return error.Canceled, - else => |err| return unexpectedErrno(err), - } - } -} - pub const PWriteError = WriteError || error{Unseekable}; /// Write to a file descriptor, with a position offset. @@ -4369,98 +4295,6 @@ pub fn send( }; } -pub const CopyFileRangeError = error{ - FileTooBig, - InputOutput, - /// `fd_in` is not open for reading; or `fd_out` is not open for writing; - /// or the `APPEND` flag is set for `fd_out`. - FilesOpenedWithWrongFlags, - IsDir, - OutOfMemory, - NoSpaceLeft, - Unseekable, - PermissionDenied, - SwapFile, - CorruptedData, -} || PReadError || PWriteError || UnexpectedError; - -/// Transfer data between file descriptors at specified offsets. -/// -/// Returns the number of bytes written, which can less than requested. -/// -/// The `copy_file_range` call copies `len` bytes from one file descriptor to another. When possible, -/// this is done within the operating system kernel, which can provide better performance -/// characteristics than transferring data from kernel to user space and back, such as with -/// `pread` and `pwrite` calls. -/// -/// `fd_in` must be a file descriptor opened for reading, and `fd_out` must be a file descriptor -/// opened for writing. They may be any kind of file descriptor; however, if `fd_in` is not a regular -/// file system file, it may cause this function to fall back to calling `pread` and `pwrite`, in which case -/// atomicity guarantees no longer apply. -/// -/// If `fd_in` and `fd_out` are the same, source and target ranges must not overlap. -/// The file descriptor seek positions are ignored and not updated. -/// When `off_in` is past the end of the input file, it successfully reads 0 bytes. -/// -/// `flags` has different meanings per operating system; refer to the respective man pages. -/// -/// These systems support in-kernel data copying: -/// * Linux (cross-filesystem from version 5.3) -/// * FreeBSD 13.0 -/// -/// Other systems fall back to calling `pread` / `pwrite`. -/// -/// Maximum offsets on Linux and FreeBSD are `maxInt(i64)`. -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 { - if (builtin.os.tag == .freebsd or builtin.os.tag == .linux) { - const use_c = native_os != .linux or - std.c.versionCheck(if (builtin.abi.isAndroid()) .{ .major = 34, .minor = 0, .patch = 0 } else .{ .major = 2, .minor = 27, .patch = 0 }); - const sys = if (use_c) std.c else linux; - - var off_in_copy: i64 = @bitCast(off_in); - var off_out_copy: i64 = @bitCast(off_out); - - while (true) { - const rc = sys.copy_file_range(fd_in, &off_in_copy, fd_out, &off_out_copy, len, flags); - if (native_os == .freebsd) { - switch (sys.errno(rc)) { - .SUCCESS => return @intCast(rc), - .BADF => return error.FilesOpenedWithWrongFlags, - .FBIG => return error.FileTooBig, - .IO => return error.InputOutput, - .ISDIR => return error.IsDir, - .NOSPC => return error.NoSpaceLeft, - .INVAL => break, // these may not be regular files, try fallback - .INTEGRITY => return error.CorruptedData, - .INTR => continue, - else => |err| return unexpectedErrno(err), - } - } else { // assume linux - switch (sys.errno(rc)) { - .SUCCESS => return @intCast(rc), - .BADF => return error.FilesOpenedWithWrongFlags, - .FBIG => return error.FileTooBig, - .IO => return error.InputOutput, - .ISDIR => return error.IsDir, - .NOSPC => return error.NoSpaceLeft, - .INVAL => break, // these may not be regular files, try fallback - .NOMEM => return error.OutOfMemory, - .OVERFLOW => return error.Unseekable, - .PERM => return error.PermissionDenied, - .TXTBSY => return error.SwapFile, - .XDEV => break, // support for cross-filesystem copy added in Linux 5.3, use fallback - else => |err| return unexpectedErrno(err), - } - } - } - } - - var buf: [8 * 4096]u8 = undefined; - const amt_read = try pread(fd_in, buf[0..@min(buf.len, len)], off_in); - if (amt_read == 0) return 0; - return pwrite(fd_out, buf[0..amt_read], off_out); -} - pub const PollError = error{ /// The network subsystem has failed. NetworkDown,