From bd287dd1942f0a72e6bd9dc8475bd4e7d34fa5f8 Mon Sep 17 00:00:00 2001 From: Terin Stock Date: Fri, 10 Jan 2020 02:03:56 -0800 Subject: [PATCH] std: implement sendfile on linux This changset adds a `sendfile(2)` syscall bindings to the linux bits component. Where available, the `sendfile64(2)` syscall will be transparently called. A wrapping function has also been added to the std.os to transform errno returns to Zig errors. Change-Id: I86769fc4382c0771e3656e7b21137bafd99a4411 --- lib/std/c/freebsd.zig | 8 +++ lib/std/os.zig | 115 ++++++++++++++++++++++++++++++++++++++++++ lib/std/os/linux.zig | 8 +++ 3 files changed, 131 insertions(+) diff --git a/lib/std/c/freebsd.zig b/lib/std/c/freebsd.zig index 4c6614c978..e7d924ddbf 100644 --- a/lib/std/c/freebsd.zig +++ b/lib/std/c/freebsd.zig @@ -8,6 +8,14 @@ pub extern "c" fn getdents(fd: c_int, buf_ptr: [*]u8, nbytes: usize) usize; pub extern "c" fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) c_int; pub extern "c" fn getrandom(buf_ptr: [*]u8, buf_len: usize, flags: c_uint) isize; +pub const sf_hdtr = extern struct { + headers: [*]iovec_const, + hdr_cnt: c_int, + trailers: [*]iovec_const, + trl_cnt: c_int, +}; +pub extern "c" fn sendfile(fd: c_int, s: c_int, offset: u64, nbytes: usize, sf_hdtr: ?*sf_hdtr, sbytes: ?*u64, flags: c_int) c_int; + pub const dl_iterate_phdr_callback = extern fn (info: *dl_phdr_info, size: usize, data: ?*c_void) c_int; pub extern "c" fn dl_iterate_phdr(callback: dl_iterate_phdr_callback, data: ?*c_void) c_int; diff --git a/lib/std/os.zig b/lib/std/os.zig index 127ada8fe5..2015b52d2f 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -3498,6 +3498,121 @@ pub fn send( return sendto(sockfd, buf, flags, null, 0); } +pub const SendFileError = error{ + /// There was an unspecified error while reading from infd. + InputOutput, + + /// There was insufficient resources for processing. + SystemResources, + + /// The value provided for count overflows the maximum size of either + /// infd or outfd. + Overflow, + + /// Offset was provided, but infd is not seekable. + Unseekable, + + /// The outfd is marked nonblocking and the requested operation would block, and + /// there is no global event loop configured. + WouldBlock, +} || WriteError || UnexpectedError; + +pub const sf_hdtr = struct { + headers: []iovec_const, + trailers: []iovec_const, +}; + +/// Transfer data between file descriptors. +/// +/// The `sendfile` call copies `count` bytes from one file descriptor to another within the kernel. This can +/// be more performant than transferring data from the kernel to user space and back, such as with +/// `read` and `write` calls. +/// +/// The `infd` should be a file descriptor opened for reading, and `outfd` should be a file descriptor +/// opened for writing. Copying will begin at `offset`, if not null, which will be updated to reflect +/// the number of bytes read. If `offset` is null, the copying will begin at the current seek position, +/// and the file position will be updated. +pub fn sendfile(infd: fd_t, outfd: fd_t, offset: u64, count: usize, optional_hdtr: ?*const sf_hdtr, flags: u32) SendFileError!usize { + // XXX: check if offset is > length of file, return 0 bytes written + // XXX: document systems where headers are sent atomically. + // XXX: compute new offset on EINTR/EAGAIN + var rc: usize = undefined; + var err: usize = undefined; + if (builtin.os == .linux) { + while (true) { + try lseek_SET(infd, offset); + + if (optional_hdtr) |hdtr| { + try writev(outfd, hdtr.headers); + } + + rc = system.sendfile(outfd, infd, null, count); + err = errno(rc); + + if (optional_hdtr) |hdtr| { + try writev(outfd, hdtr.trailers); + } + + switch (err) { + 0 => return @intCast(usize, rc), + else => return unexpectedErrno(err), + + EBADF => unreachable, + EINVAL => unreachable, + EFAULT => unreachable, + EAGAIN => if (std.event.Loop.instance) |loop| { + loop.waitUntilFdWritable(outfd); + continue; + } else { + return error.WouldBlock; + }, + EIO => return error.InputOutput, + ENOMEM => return error.SystemResources, + EOVERFLOW => return error.Overflow, + ESPIPE => return error.Unseekable, + } + } + } else if (builtin.os == .freebsd) { + while (true) { + var rcount: u64 = 0; + var hdtr: std.c.sf_hdtr = undefined; + if (optional_hdtr) |h| { + hdtr = std.c.sf_hdtr{ + .headers = h.headers.ptr, + .hdr_cnt = @intCast(c_int, h.headers.len), + .trailers = h.trailers.ptr, + .trl_cnt = @intCast(c_int, h.trailers.len), + }; + } + err = errno(system.sendfile(infd, outfd, offset, count, &hdtr, &rcount, @intCast(c_int, flags))); + switch (err) { + 0 => return @intCast(usize, rcount), + else => return unexpectedErrno(err), + + EBADF => unreachable, + EFAULT => unreachable, + EINVAL => unreachable, + ENOTCAPABLE => unreachable, + ENOTCONN => unreachable, + ENOTSOCK => unreachable, + EAGAIN => if (std.event.Loop.instance) |loop| { + loop.waitUntilFdWritable(outfd); + continue; + } else { + return error.WouldBlock; + }, + EBUSY => return error.DeviceBusy, + EINTR => continue, + EIO => return error.InputOutput, + ENOBUFS => return error.SystemResources, + EPIPE => return error.BrokenPipe, + } + } + } else { + @compileError("sendfile unimplemented for this target"); + } +} + pub const PollError = error{ /// The kernel had no space to allocate file descriptor tables. SystemResources, diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 30dba85e51..95b1018a6b 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -846,6 +846,14 @@ pub fn sendto(fd: i32, buf: [*]const u8, len: usize, flags: u32, addr: ?*const s return syscall6(SYS_sendto, @bitCast(usize, @as(isize, fd)), @ptrToInt(buf), len, flags, @ptrToInt(addr), @intCast(usize, alen)); } +pub fn sendfile(outfd: i32, infd: i32, offset: ?*u64, count: usize) usize { + if (@hasDecl(@This(), "SYS_sendfile64")) { + return syscall4(SYS_sendfile64, @bitCast(usize, @as(isize, outfd)), @bitCast(usize, @as(isize, infd)), @ptrToInt(offset), count); + } else { + return syscall4(SYS_sendfile, @bitCast(usize, @as(isize, outfd)), @bitCast(usize, @as(isize, infd)), @ptrToInt(offset), count); + } +} + pub fn socketpair(domain: i32, socket_type: i32, protocol: i32, fd: [2]i32) usize { if (builtin.arch == .i386) { return socketcall(SC_socketpair, &[4]usize{ @intCast(usize, domain), @intCast(usize, socket_type), @intCast(usize, protocol), @ptrToInt(&fd[0]) });