Skip to content

Commit 4854d41

Browse files
committed
do direct splice syscall and probe availability to get android builds to work
Android builds use feature level 14, the libc wrapper for splice is gated on feature level 21+ so we have to invoke the syscall directly. Additionally the emulator doesn't seem to support it so we also have to add ENOSYS checks.
1 parent 3dfc377 commit 4854d41

File tree

2 files changed

+72
-4
lines changed

2 files changed

+72
-4
lines changed

library/std/src/sys/unix/kernel_copy.rs

+38-4
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ use crate::os::unix::fs::FileTypeExt;
5858
use crate::os::unix::io::{AsRawFd, FromRawFd, RawFd};
5959
use crate::process::{ChildStderr, ChildStdin, ChildStdout};
6060
use crate::ptr;
61+
use crate::sync::atomic::{AtomicBool, Ordering};
6162
use crate::sys::cvt;
6263

6364
#[cfg(test)]
@@ -440,7 +441,6 @@ pub(super) enum CopyResult {
440441
/// If the initial file offset was 0 then `Fallback` will only contain `0`.
441442
pub(super) fn copy_regular_files(reader: RawFd, writer: RawFd, max_len: u64) -> CopyResult {
442443
use crate::cmp;
443-
use crate::sync::atomic::{AtomicBool, Ordering};
444444

445445
// Kernel prior to 4.5 don't have copy_file_range
446446
// We store the availability in a global to avoid unnecessary syscalls
@@ -534,6 +534,30 @@ enum SpliceMode {
534534
/// performs splice or sendfile between file descriptors
535535
/// Does _not_ fall back to a generic copy loop.
536536
fn sendfile_splice(mode: SpliceMode, reader: RawFd, writer: RawFd, len: u64) -> CopyResult {
537+
static HAS_SENDFILE: AtomicBool = AtomicBool::new(true);
538+
static HAS_SPLICE: AtomicBool = AtomicBool::new(true);
539+
540+
syscall! {
541+
fn splice(
542+
srcfd: libc::c_int,
543+
src_offset: *const i64,
544+
dstfd: libc::c_int,
545+
dst_offset: *const i64,
546+
len: libc::size_t,
547+
flags: libc::c_int
548+
) -> libc::ssize_t
549+
}
550+
551+
match mode {
552+
SpliceMode::Sendfile if !HAS_SENDFILE.load(Ordering::Relaxed) => {
553+
return CopyResult::Fallback(0);
554+
}
555+
SpliceMode::Splice if !HAS_SPLICE.load(Ordering::Relaxed) => {
556+
return CopyResult::Fallback(0);
557+
}
558+
_ => (),
559+
}
560+
537561
let mut written = 0u64;
538562
while written < len {
539563
let chunk_size = crate::cmp::min(len - written, 0x7ffff000_u64) as usize;
@@ -543,7 +567,7 @@ fn sendfile_splice(mode: SpliceMode, reader: RawFd, writer: RawFd, len: u64) ->
543567
cvt(unsafe { libc::sendfile(writer, reader, ptr::null_mut(), chunk_size) })
544568
}
545569
SpliceMode::Splice => cvt(unsafe {
546-
libc::splice(reader, ptr::null_mut(), writer, ptr::null_mut(), chunk_size, 0)
570+
splice(reader, ptr::null_mut(), writer, ptr::null_mut(), chunk_size, 0)
547571
}),
548572
};
549573

@@ -552,8 +576,18 @@ fn sendfile_splice(mode: SpliceMode, reader: RawFd, writer: RawFd, len: u64) ->
552576
Ok(ret) => written += ret as u64,
553577
Err(err) => {
554578
return match err.raw_os_error() {
555-
Some(os_err) if os_err == libc::EINVAL => {
556-
// splice/sendfile do not support this particular file descritor (EINVAL)
579+
Some(libc::ENOSYS | libc::EPERM) => {
580+
// syscall not supported (ENOSYS)
581+
// syscall is disallowed, e.g. by seccomp (EPERM)
582+
match mode {
583+
SpliceMode::Sendfile => HAS_SENDFILE.store(false, Ordering::Relaxed),
584+
SpliceMode::Splice => HAS_SPLICE.store(false, Ordering::Relaxed),
585+
}
586+
assert_eq!(written, 0);
587+
CopyResult::Fallback(0)
588+
}
589+
Some(libc::EINVAL) => {
590+
// splice/sendfile do not support this particular file descriptor (EINVAL)
557591
assert_eq!(written, 0);
558592
CopyResult::Fallback(0)
559593
}

library/std/src/sys/unix/kernel_copy/tests.rs

+34
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ fn bench_file_to_socket_copy(b: &mut test::Bencher) {
121121
#[cfg(any(target_os = "linux", target_os = "android"))]
122122
#[bench]
123123
fn bench_socket_pipe_socket_copy(b: &mut test::Bencher) {
124+
use super::CopyResult;
124125
use crate::io::ErrorKind;
125126
use crate::process::{ChildStdin, ChildStdout};
126127
use crate::sys_common::FromInner;
@@ -135,6 +136,21 @@ fn bench_socket_pipe_socket_copy(b: &mut test::Bencher) {
135136

136137
let local_end = crate::sync::Arc::new(acceptor.accept().unwrap().0);
137138

139+
// the data flow in this benchmark:
140+
//
141+
// socket(tx) local_source
142+
// remote_end (write) +--------> (splice to)
143+
// write_end
144+
// +
145+
// |
146+
// | pipe
147+
// v
148+
// read_end
149+
// remote_end (read) <---------+ (splice to) *
150+
// socket(rx) local_end
151+
//
152+
// * benchmark loop using io::copy
153+
138154
crate::thread::spawn(move || {
139155
let mut sink_buf = vec![0u8; 1024 * 1024];
140156
remote_end.set_nonblocking(true).unwrap();
@@ -156,6 +172,24 @@ fn bench_socket_pipe_socket_copy(b: &mut test::Bencher) {
156172
}
157173
});
158174

175+
// check that splice works, otherwise the benchmark would hang
176+
let probe = super::sendfile_splice(
177+
super::SpliceMode::Splice,
178+
local_end.as_raw_fd(),
179+
write_end.as_raw_fd(),
180+
1,
181+
);
182+
183+
match probe {
184+
CopyResult::Ended(Ok(1)) => {
185+
// splice works
186+
}
187+
_ => {
188+
eprintln!("splice failed, skipping benchmark");
189+
return;
190+
}
191+
}
192+
159193
let local_source = local_end.clone();
160194
crate::thread::spawn(move || {
161195
loop {

0 commit comments

Comments
 (0)