mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 05:44:20 +00:00
std: extract sendfile/copy_file_range from Io.File.Writer
and move it into std.Io.Threaded (below the VTable)
This commit is contained in:
parent
4f6658a67b
commit
fc8b792251
11 changed files with 1015 additions and 834 deletions
|
|
@ -662,8 +662,8 @@ pub const VTable = struct {
|
||||||
conditionWaitUncancelable: *const fn (?*anyopaque, cond: *Condition, mutex: *Mutex) void,
|
conditionWaitUncancelable: *const fn (?*anyopaque, cond: *Condition, mutex: *Mutex) void,
|
||||||
conditionWake: *const fn (?*anyopaque, cond: *Condition, wake: Condition.Wake) void,
|
conditionWake: *const fn (?*anyopaque, cond: *Condition, wake: Condition.Wake) void,
|
||||||
|
|
||||||
dirMake: *const fn (?*anyopaque, Dir, []const u8, Dir.Mode) Dir.MakeError!void,
|
dirMake: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.MakeError!void,
|
||||||
dirMakePath: *const fn (?*anyopaque, Dir, []const u8, Dir.Mode) Dir.MakePathError!Dir.MakePathStatus,
|
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,
|
dirMakeOpenPath: *const fn (?*anyopaque, Dir, []const u8, Dir.OpenOptions) Dir.MakeOpenPathError!Dir,
|
||||||
dirStat: *const fn (?*anyopaque, Dir) Dir.StatError!Dir.Stat,
|
dirStat: *const fn (?*anyopaque, Dir) Dir.StatError!Dir.Stat,
|
||||||
dirStatPath: *const fn (?*anyopaque, Dir, []const u8, Dir.StatPathOptions) Dir.StatPathError!File.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,
|
fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat,
|
||||||
fileLength: *const fn (?*anyopaque, File) File.LengthError!u64,
|
fileLength: *const fn (?*anyopaque, File) File.LengthError!u64,
|
||||||
fileClose: *const fn (?*anyopaque, File) void,
|
fileClose: *const fn (?*anyopaque, File) void,
|
||||||
fileWriteStreaming: *const fn (?*anyopaque, File, buffer: [][]const u8) File.WriteStreamingError!usize,
|
fileWriteStreaming: *const fn (?*anyopaque, File, header: []const u8, data: []const []const u8, splat: usize) File.Writer.Error!usize,
|
||||||
fileWritePositional: *const fn (?*anyopaque, File, buffer: [][]const u8, offset: u64) File.WritePositionalError!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.
|
/// Returns 0 on end of stream.
|
||||||
fileReadStreaming: *const fn (?*anyopaque, File, data: [][]u8) File.Reader.Error!usize,
|
fileReadStreaming: *const fn (?*anyopaque, File, data: [][]u8) File.Reader.Error!usize,
|
||||||
/// Returns 0 on end of stream.
|
/// Returns 0 on end of stream.
|
||||||
|
|
@ -724,6 +726,7 @@ pub const VTable = struct {
|
||||||
/// Returns 0 on end of stream.
|
/// Returns 0 on end of stream.
|
||||||
netRead: *const fn (?*anyopaque, src: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize,
|
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,
|
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,
|
netClose: *const fn (?*anyopaque, handle: net.Socket.Handle) void,
|
||||||
netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface,
|
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,
|
netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name,
|
||||||
|
|
|
||||||
|
|
@ -652,7 +652,6 @@ pub const RealPathError = error{
|
||||||
NoSpaceLeft,
|
NoSpaceLeft,
|
||||||
FileSystem,
|
FileSystem,
|
||||||
DeviceBusy,
|
DeviceBusy,
|
||||||
ProcessNotFound,
|
|
||||||
SharingViolation,
|
SharingViolation,
|
||||||
PipeBusy,
|
PipeBusy,
|
||||||
/// Windows: file paths provided by the user must be valid WTF-8.
|
/// Windows: file paths provided by the user must be valid WTF-8.
|
||||||
|
|
@ -1038,7 +1037,6 @@ pub const DeleteTreeError = error{
|
||||||
FileSystem,
|
FileSystem,
|
||||||
FileBusy,
|
FileBusy,
|
||||||
DeviceBusy,
|
DeviceBusy,
|
||||||
ProcessNotFound,
|
|
||||||
/// One of the path components was not a directory.
|
/// One of the path components was not a directory.
|
||||||
/// This error is unreachable if `sub_path` does not contain a path separator.
|
/// This error is unreachable if `sub_path` does not contain a path separator.
|
||||||
NotDir,
|
NotDir,
|
||||||
|
|
|
||||||
|
|
@ -198,7 +198,6 @@ pub const OpenError = error{
|
||||||
NoDevice,
|
NoDevice,
|
||||||
/// On Windows, `\\server` or `\\server\share` was not found.
|
/// On Windows, `\\server` or `\\server\share` was not found.
|
||||||
NetworkNotFound,
|
NetworkNotFound,
|
||||||
ProcessNotFound,
|
|
||||||
/// On Windows, antivirus software is enabled by default. It can be
|
/// On Windows, antivirus software is enabled by default. It can be
|
||||||
/// disabled, but Windows Update sometimes ignores the user's preference
|
/// disabled, but Windows Update sometimes ignores the user's preference
|
||||||
/// and re-enables it. When enabled, antivirus software on Windows
|
/// 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);
|
return io.vtable.fileReadPositional(io.userdata, file, buffer, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const WriteStreamingError = error{} || Io.UnexpectedError || Io.Cancelable;
|
pub const WritePositionalError = Writer.Error || error{Unseekable};
|
||||||
|
|
||||||
pub fn writeStreaming(file: File, io: Io, buffer: [][]const u8) WriteStreamingError!usize {
|
|
||||||
return file.fileWriteStreaming(io, buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const WritePositionalError = WriteStreamingError || error{Unseekable};
|
|
||||||
|
|
||||||
pub fn writePositional(file: File, io: Io, buffer: [][]const u8, offset: u64) WritePositionalError!usize {
|
pub fn writePositional(file: File, io: Io, buffer: [][]const u8, offset: u64) WritePositionalError!usize {
|
||||||
return io.vtable.fileWritePositional(io.userdata, file, buffer, offset);
|
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
|
/// Opens a file for reading or writing, without attempting to create a new
|
||||||
/// file, based on an absolute path.
|
/// file, based on an absolute path.
|
||||||
///
|
///
|
||||||
|
|
@ -511,6 +517,8 @@ pub const SeekError = error{
|
||||||
AccessDenied,
|
AccessDenied,
|
||||||
} || Io.Cancelable || Io.UnexpectedError;
|
} || Io.Cancelable || Io.UnexpectedError;
|
||||||
|
|
||||||
|
pub const WriteFilePositionalError = Writer.WriteFileError || error{Unseekable};
|
||||||
|
|
||||||
/// Defaults to positional reading; falls back to streaming.
|
/// Defaults to positional reading; falls back to streaming.
|
||||||
///
|
///
|
||||||
/// Positional is more threadsafe, since the global seek position is not
|
/// Positional is more threadsafe, since the global seek position is not
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,13 @@ const assert = std.debug.assert;
|
||||||
io: Io,
|
io: Io,
|
||||||
file: File,
|
file: File,
|
||||||
err: ?Error = null,
|
err: ?Error = null,
|
||||||
mode: Reader.Mode = .positional,
|
mode: Mode = .positional,
|
||||||
/// Tracks the true seek position in the file. To obtain the logical
|
/// Tracks the true seek position in the file. To obtain the logical
|
||||||
/// position, use `logicalPos`.
|
/// position, use `logicalPos`.
|
||||||
pos: u64 = 0,
|
pos: u64 = 0,
|
||||||
size: ?u64 = null,
|
size: ?u64 = null,
|
||||||
size_err: ?SizeError = null,
|
size_err: ?SizeError = null,
|
||||||
seek_err: ?Reader.SeekError = null,
|
seek_err: ?SeekError = null,
|
||||||
interface: Io.Reader,
|
interface: Io.Reader,
|
||||||
|
|
||||||
pub const Error = error{
|
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.
|
/// trying to read a directory file descriptor as if it were a file.
|
||||||
NotOpenForReading,
|
NotOpenForReading,
|
||||||
SocketUnconnected,
|
SocketUnconnected,
|
||||||
/// This error occurs when no global event loop is configured,
|
/// Non-blocking has been enabled, and reading from the file descriptor
|
||||||
/// and reading from the file descriptor would block.
|
/// would block.
|
||||||
WouldBlock,
|
WouldBlock,
|
||||||
/// In WASI, this error occurs when the file descriptor does
|
/// In WASI, this error occurs when the file descriptor does
|
||||||
/// not hold the required rights to read from it.
|
/// not hold the required rights to read from it.
|
||||||
AccessDenied,
|
AccessDenied,
|
||||||
/// This error occurs in Linux if the process to be read from
|
|
||||||
/// no longer exists.
|
|
||||||
ProcessNotFound,
|
|
||||||
/// Unable to read file due to lock.
|
/// Unable to read file due to lock.
|
||||||
LockViolation,
|
LockViolation,
|
||||||
} || Io.Cancelable || Io.UnexpectedError;
|
} || Io.Cancelable || Io.UnexpectedError;
|
||||||
|
|
@ -93,9 +90,9 @@ pub const Mode = enum {
|
||||||
pub fn initInterface(buffer: []u8) Io.Reader {
|
pub fn initInterface(buffer: []u8) Io.Reader {
|
||||||
return .{
|
return .{
|
||||||
.vtable = &.{
|
.vtable = &.{
|
||||||
.stream = Reader.stream,
|
.stream = stream,
|
||||||
.discard = Reader.discard,
|
.discard = discard,
|
||||||
.readVec = Reader.readVec,
|
.readVec = readVec,
|
||||||
},
|
},
|
||||||
.buffer = buffer,
|
.buffer = buffer,
|
||||||
.seek = 0,
|
.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;
|
const io = r.io;
|
||||||
switch (r.mode) {
|
switch (r.mode) {
|
||||||
.positional, .positional_reading => {
|
.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.
|
/// 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;
|
const io = r.io;
|
||||||
switch (r.mode) {
|
switch (r.mode) {
|
||||||
.positional, .positional_reading => {
|
.positional, .positional_reading => {
|
||||||
|
|
@ -191,7 +188,7 @@ pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void {
|
||||||
},
|
},
|
||||||
.streaming, .streaming_reading => {
|
.streaming, .streaming_reading => {
|
||||||
const logical_pos = logicalPos(r);
|
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;
|
if (r.seek_err) |err| return err;
|
||||||
io.vtable.fileSeekTo(io.userdata, r.file, offset) catch |err| {
|
io.vtable.fileSeekTo(io.userdata, r.file, offset) catch |err| {
|
||||||
r.seek_err = 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);
|
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) {
|
switch (mode) {
|
||||||
.positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) {
|
.positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) {
|
||||||
error.Unimplemented => {
|
error.Unimplemented => {
|
||||||
|
|
|
||||||
|
|
@ -1,53 +1,38 @@
|
||||||
const Writer = @This();
|
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 std = @import("../../std.zig");
|
||||||
const Io = std.Io;
|
const Io = std.Io;
|
||||||
const File = std.Io.File;
|
const File = std.Io.File;
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const windows = std.os.windows;
|
|
||||||
const posix = std.posix;
|
|
||||||
|
|
||||||
|
io: Io,
|
||||||
file: File,
|
file: File,
|
||||||
err: ?File.WriteError = null,
|
err: ?File.WriteError = null,
|
||||||
mode: Writer.Mode = .positional,
|
mode: Mode = .positional,
|
||||||
/// Tracks the true seek position in the file. To obtain the logical
|
/// Tracks the true seek position in the file. To obtain the logical
|
||||||
/// position, add the buffer size to this value.
|
/// position, add the buffer size to this value.
|
||||||
pos: u64 = 0,
|
pos: u64 = 0,
|
||||||
sendfile_err: ?SendfileError = null,
|
write_file_err: ?WriteFileError = null,
|
||||||
copy_file_range_err: ?CopyFileRangeError = null,
|
seek_err: ?SeekError = null,
|
||||||
fcopyfile_err: ?FcopyfileError = null,
|
|
||||||
seek_err: ?Writer.SeekError = null,
|
|
||||||
interface: Io.Writer,
|
interface: Io.Writer,
|
||||||
|
|
||||||
pub const Mode = File.Reader.Mode;
|
pub const Mode = File.Reader.Mode;
|
||||||
|
|
||||||
pub const SendfileError = error{
|
pub const WriteFileError = error{
|
||||||
UnsupportedOperation,
|
/// `out_fd` is an unconnected socket, or out_fd closed its read end.
|
||||||
SystemResources,
|
|
||||||
InputOutput,
|
|
||||||
BrokenPipe,
|
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,
|
WouldBlock,
|
||||||
Unexpected,
|
/// Unspecified error while reading from in_fd.
|
||||||
};
|
InputOutput,
|
||||||
|
/// Insufficient kernel memory to read from in_fd.
|
||||||
pub const CopyFileRangeError = std.os.freebsd.CopyFileRangeError || std.os.linux.wrapped.CopyFileRangeError;
|
SystemResources,
|
||||||
|
} || Io.Cancelable || Io.UnexpectedError;
|
||||||
pub const FcopyfileError = error{
|
|
||||||
OperationNotSupported,
|
|
||||||
OutOfMemory,
|
|
||||||
Unexpected,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const SeekError = Io.File.SeekError;
|
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 {
|
pub fn init(file: File, buffer: []u8) Writer {
|
||||||
return .{
|
return .{
|
||||||
.file = file,
|
.file = file,
|
||||||
|
|
@ -91,121 +76,17 @@ pub fn moveToReader(w: *Writer) File.Reader {
|
||||||
|
|
||||||
pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
|
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 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) {
|
switch (w.mode) {
|
||||||
.positional, .positional_reading => {
|
.positional, .positional_reading => return drainPositional(w, data, splat),
|
||||||
const n = posix.pwritev(handle, iovecs[0..len], w.pos) catch |err| switch (err) {
|
.streaming, .streaming_reading => return drainStreaming(w, data, splat),
|
||||||
|
.failure => return error.WriteFailed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 => {
|
error.Unseekable => {
|
||||||
w.mode = w.mode.toStreaming();
|
w.mode = w.mode.toStreaming();
|
||||||
const pos = w.pos;
|
const pos = w.pos;
|
||||||
|
|
@ -224,312 +105,93 @@ pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
w.pos += n;
|
w.pos += n;
|
||||||
return io_w.consume(n);
|
return w.interface.consume(n);
|
||||||
},
|
}
|
||||||
.streaming, .streaming_reading => {
|
|
||||||
const n = posix.writev(handle, iovecs[0..len]) catch |err| {
|
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;
|
w.err = err;
|
||||||
return error.WriteFailed;
|
return error.WriteFailed;
|
||||||
};
|
};
|
||||||
w.pos += n;
|
w.pos += n;
|
||||||
return io_w.consume(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,
|
.failure => return error.WriteFailed,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sendFile(
|
fn sendFilePositional(w: *Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize {
|
||||||
io_w: *Io.Writer,
|
const io = w.io;
|
||||||
file_reader: *Io.File.Reader,
|
const header = w.interface.buffered();
|
||||||
limit: Io.Limit,
|
const n = io.vtable.fileSendFilePositional(io.userdata, w.file, header, file_reader, limit, w.pos) catch |err| switch (err) {
|
||||||
) 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 => {
|
error.Unseekable => {
|
||||||
file_reader.mode = file_reader.mode.toStreaming();
|
w.mode = w.mode.toStreaming();
|
||||||
const pos = file_reader.pos;
|
const pos = w.pos;
|
||||||
if (pos != 0) {
|
if (pos != 0) {
|
||||||
file_reader.pos = 0;
|
w.pos = 0;
|
||||||
file_reader.seekBy(@intCast(pos)) catch {
|
w.seekTo(@intCast(pos)) catch {
|
||||||
file_reader.mode = .failure;
|
w.mode = .failure;
|
||||||
return error.ReadFailed;
|
return error.WriteFailed;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
},
|
},
|
||||||
|
error.Canceled => {
|
||||||
|
w.err = error.Canceled;
|
||||||
|
return error.WriteFailed;
|
||||||
|
},
|
||||||
else => |e| {
|
else => |e| {
|
||||||
w.sendfile_err = e;
|
w.write_file_err = e;
|
||||||
return 0;
|
return error.WriteFailed;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if (n == 0) {
|
|
||||||
file_reader.size = file_reader.pos;
|
|
||||||
return error.EndOfStream;
|
|
||||||
}
|
|
||||||
file_reader.pos += n;
|
|
||||||
w.pos += n;
|
w.pos += n;
|
||||||
return n;
|
return w.interface.consume(n);
|
||||||
}
|
|
||||||
|
|
||||||
const copy_file_range = switch (native_os) {
|
|
||||||
.freebsd => std.os.freebsd.copy_file_range,
|
|
||||||
.linux => std.os.linux.wrapped.copy_file_range,
|
|
||||||
else => {},
|
|
||||||
};
|
|
||||||
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;
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sendFileBuffered(
|
fn sendFileStreaming(w: *Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize {
|
||||||
io_w: *Io.Writer,
|
const io = w.io;
|
||||||
file_reader: *Io.File.Reader,
|
const header = w.interface.buffered();
|
||||||
reader_buffered: []const u8,
|
const n = io.vtable.fileSendFileStreaming(io.userdata, w.file, header, file_reader, limit) catch |err| switch (err) {
|
||||||
) Io.Writer.FileError!usize {
|
error.Canceled => {
|
||||||
const n = try drain(io_w, &.{reader_buffered}, 1);
|
w.err = error.Canceled;
|
||||||
file_reader.seekBy(@intCast(n)) catch return error.ReadFailed;
|
return error.WriteFailed;
|
||||||
return n;
|
},
|
||||||
|
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 w.interface.flush();
|
||||||
try seekToUnbuffered(w, offset);
|
try seekToUnbuffered(w, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Asserts that no data is currently buffered.
|
/// 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);
|
assert(w.interface.buffered().len == 0);
|
||||||
|
const io = w.io;
|
||||||
switch (w.mode) {
|
switch (w.mode) {
|
||||||
.positional, .positional_reading => {
|
.positional, .positional_reading => {
|
||||||
w.pos = offset;
|
w.pos = offset;
|
||||||
},
|
},
|
||||||
.streaming, .streaming_reading => {
|
.streaming, .streaming_reading => {
|
||||||
if (w.seek_err) |err| return err;
|
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;
|
w.seek_err = err;
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
|
|
@ -553,7 +215,7 @@ pub fn end(w: *Writer) EndError!void {
|
||||||
switch (w.mode) {
|
switch (w.mode) {
|
||||||
.positional,
|
.positional,
|
||||||
.positional_reading,
|
.positional_reading,
|
||||||
=> w.file.setEndPos(w.pos) catch |err| switch (err) {
|
=> w.file.setLength(w.pos) catch |err| switch (err) {
|
||||||
error.NonResizable => return,
|
error.NonResizable => return,
|
||||||
else => |e| return e,
|
else => |e| return e,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ const Threaded = @This();
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const native_os = builtin.os.tag;
|
const native_os = builtin.os.tag;
|
||||||
const is_windows = native_os == .windows;
|
const is_windows = native_os == .windows;
|
||||||
|
const is_darwin = native_os.isDarwin();
|
||||||
const windows = std.os.windows;
|
const windows = std.os.windows;
|
||||||
const ws2_32 = std.os.windows.ws2_32;
|
const ws2_32 = std.os.windows.ws2_32;
|
||||||
const is_debug = builtin.mode == .Debug;
|
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_io: if (have_sig_io) posix.Sigaction else void,
|
||||||
old_sig_pipe: if (have_sig_pipe) 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 {
|
pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum {
|
||||||
enabled,
|
enabled,
|
||||||
disabled,
|
disabled,
|
||||||
|
|
@ -82,6 +87,33 @@ pub const Pid = if (native_os == .linux) enum(posix.pid_t) {
|
||||||
_,
|
_,
|
||||||
} else enum(u0) { unknown = 0 };
|
} 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 {
|
const Thread = struct {
|
||||||
/// The value that needs to be passed to pthread_kill or tgkill in order to
|
/// The value that needs to be passed to pthread_kill or tgkill in order to
|
||||||
/// send a signal.
|
/// send a signal.
|
||||||
|
|
@ -436,6 +468,8 @@ pub fn io(t: *Threaded) Io {
|
||||||
.fileClose = fileClose,
|
.fileClose = fileClose,
|
||||||
.fileWriteStreaming = fileWriteStreaming,
|
.fileWriteStreaming = fileWriteStreaming,
|
||||||
.fileWritePositional = fileWritePositional,
|
.fileWritePositional = fileWritePositional,
|
||||||
|
.fileWriteFileStreaming = fileWriteFileStreaming,
|
||||||
|
.fileWriteFilePositional = fileWriteFilePositional,
|
||||||
.fileReadStreaming = fileReadStreaming,
|
.fileReadStreaming = fileReadStreaming,
|
||||||
.fileReadPositional = fileReadPositional,
|
.fileReadPositional = fileReadPositional,
|
||||||
.fileSeekBy = fileSeekBy,
|
.fileSeekBy = fileSeekBy,
|
||||||
|
|
@ -556,6 +590,8 @@ pub fn ioBasic(t: *Threaded) Io {
|
||||||
.fileClose = fileClose,
|
.fileClose = fileClose,
|
||||||
.fileWriteStreaming = fileWriteStreaming,
|
.fileWriteStreaming = fileWriteStreaming,
|
||||||
.fileWritePositional = fileWritePositional,
|
.fileWritePositional = fileWritePositional,
|
||||||
|
.fileWriteFileStreaming = fileWriteFileStreaming,
|
||||||
|
.fileWriteFilePositional = fileWriteFilePositional,
|
||||||
.fileReadStreaming = fileReadStreaming,
|
.fileReadStreaming = fileReadStreaming,
|
||||||
.fileReadPositional = fileReadPositional,
|
.fileReadPositional = fileReadPositional,
|
||||||
.fileSeekBy = fileSeekBy,
|
.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_accept4 = !socket_flags_unsupported;
|
||||||
const have_flock_open_flags = @hasField(posix.O, "EXLOCK");
|
const have_flock_open_flags = @hasField(posix.O, "EXLOCK");
|
||||||
const have_networking = native_os != .wasi;
|
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_io = posix.SIG != void and @hasField(posix.SIG, "IO");
|
||||||
const have_sig_pipe = posix.SIG != void and @hasField(posix.SIG, "PIPE");
|
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 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;
|
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 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 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 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:
|
/// Trailing data:
|
||||||
/// 1. context
|
/// 1. context
|
||||||
|
|
@ -5715,27 +5769,828 @@ fn openSelfExe(userdata: ?*anyopaque, flags: Io.File.OpenFlags) Io.File.OpenSelf
|
||||||
fn fileWritePositional(
|
fn fileWritePositional(
|
||||||
userdata: ?*anyopaque,
|
userdata: ?*anyopaque,
|
||||||
file: Io.File,
|
file: Io.File,
|
||||||
buffer: [][]const u8,
|
header: []const u8,
|
||||||
|
data: []const []const u8,
|
||||||
|
splat: usize,
|
||||||
offset: u64,
|
offset: u64,
|
||||||
) Io.File.WritePositionalError!usize {
|
) Io.File.WritePositionalError!usize {
|
||||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
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) {
|
while (true) {
|
||||||
_ = file;
|
switch (std.os.wasi.fd_pwrite(file.handle, &iovecs, iovlen, offset, &n_written)) {
|
||||||
_ = buffer;
|
.SUCCESS => {
|
||||||
_ = offset;
|
current_thread.endSyscall();
|
||||||
@panic("TODO implement fileWritePositional");
|
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) {
|
||||||
|
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));
|
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||||
_ = t;
|
const current_thread = Thread.getCurrent(t);
|
||||||
while (true) {
|
|
||||||
_ = file;
|
if (is_windows) @panic("TODO");
|
||||||
_ = buffer;
|
|
||||||
@panic("TODO implement fileWriteStreaming");
|
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 {
|
fn nowPosix(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
|
||||||
|
|
@ -7666,6 +8521,7 @@ fn netWritePosix(
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const flags = posix.MSG.NOSIGNAL;
|
const flags = posix.MSG.NOSIGNAL;
|
||||||
|
|
||||||
try current_thread.beginSyscall();
|
try current_thread.beginSyscall();
|
||||||
while (true) {
|
while (true) {
|
||||||
const rc = posix.system.sendmsg(fd, &msg, flags);
|
const rc = posix.system.sendmsg(fd, &msg, flags);
|
||||||
|
|
@ -7829,7 +8685,11 @@ fn netWriteUnavailable(
|
||||||
return error.NetworkDown;
|
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.
|
// OS checks ptr addr before length so zero length vectors must be omitted.
|
||||||
if (bytes.len == 0) return;
|
if (bytes.len == 0) return;
|
||||||
if (v.len - i.* == 0) return;
|
if (v.len - i.* == 0) return;
|
||||||
|
|
@ -8152,7 +9012,7 @@ fn netLookupFallible(
|
||||||
// TODO use dnsres_getaddrinfo
|
// TODO use dnsres_getaddrinfo
|
||||||
}
|
}
|
||||||
|
|
||||||
if (native_os.isDarwin()) {
|
if (is_darwin) {
|
||||||
// TODO use CFHostStartInfoResolution / CFHostCancelInfoResolution
|
// TODO use CFHostStartInfoResolution / CFHostCancelInfoResolution
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ pub const uefi = @import("os/uefi.zig");
|
||||||
pub const wasi = @import("os/wasi.zig");
|
pub const wasi = @import("os/wasi.zig");
|
||||||
pub const emscripten = @import("os/emscripten.zig");
|
pub const emscripten = @import("os/emscripten.zig");
|
||||||
pub const windows = @import("os/windows.zig");
|
pub const windows = @import("os/windows.zig");
|
||||||
pub const freebsd = @import("os/freebsd.zig");
|
|
||||||
|
|
||||||
test {
|
test {
|
||||||
_ = linux;
|
_ = linux;
|
||||||
|
|
|
||||||
|
|
@ -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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -9855,125 +9855,3 @@ pub const cmsghdr = extern struct {
|
||||||
level: i32,
|
level: i32,
|
||||||
type: 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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -405,14 +405,6 @@ test "futex2_requeue" {
|
||||||
try expectEqual(0, rc);
|
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 {
|
test {
|
||||||
_ = linux.IoUring;
|
_ = linux.IoUring;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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};
|
pub const PWriteError = WriteError || error{Unseekable};
|
||||||
|
|
||||||
/// Write to a file descriptor, with a position offset.
|
/// 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{
|
pub const PollError = error{
|
||||||
/// The network subsystem has failed.
|
/// The network subsystem has failed.
|
||||||
NetworkDown,
|
NetworkDown,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue