From 719c3eec3817fb227264305036cc3237ca0c72e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 8 Nov 2026 20:43:41 +0100 Subject: [PATCH] fix(turnloop): don't probe epoll_pwait2 on Android Android's app seccomp filter doesn't list epoll_pwait2, so the construction-time probe kills the process with SIGSYS instead of failing with ENOSYS. Android takes the timerfd path older kernels use. Upstreams Perry f3d71e8a23, which patched Perry's vendored copy. Claude-Session: https://claude.ai/code/session_01RuKCUpDf2GnMNg4owQ7HuU --- crates/turnloop/src/backend/epoll.rs | 52 ++++++++++++++++------------ 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/crates/turnloop/src/backend/epoll.rs b/crates/turnloop/src/backend/epoll.rs index ad57722..d67e239 100644 --- a/crates/turnloop/src/backend/epoll.rs +++ b/crates/turnloop/src/backend/epoll.rs @@ -1,6 +1,7 @@ //! Edge-triggered epoll with eventfd wake and nanosecond wait budgets. //! epoll_pwait2 support is probed at construction, never with a second wait in a -//! turn. Older kernels use a one-shot timerfd plus one epoll_wait(-1). +//! turn. Older kernels, and Android (whose seccomp filter kills the probe), use a +//! one-shot timerfd plus one epoll_wait(-1). use crate::{ Result, backend::{ @@ -99,29 +100,34 @@ impl Poller for Epoll { WAKE, libc::EPOLLIN | libc::EPOLLET, )?; - let mut event = libc::epoll_event { events: 0, u64: 0 }; - let zero = timespec(Duration::ZERO); - // SAFETY: valid epoll descriptor, one event output slot, zero timeout, and - // null signal mask. The Linux kernel sigset ABI is eight bytes. - let probe = unsafe { - libc::syscall( - libc::SYS_epoll_pwait2, - fd.as_raw_fd(), - &mut event, - 1, - &zero, - std::ptr::null::(), - 8usize, - ) - }; - let unsupported = if probe < 0 { - let e = std::io::Error::last_os_error(); - if e.raw_os_error() != Some(libc::ENOSYS) { - return Err(e.into()); + // Android's app seccomp filter doesn't list epoll_pwait2: the call + // kills the process with SIGSYS instead of failing with ENOSYS, so + // there it isn't probed and the timerfd path is the only one. + let unsupported = cfg!(target_os = "android") || { + let mut event = libc::epoll_event { events: 0, u64: 0 }; + let zero = timespec(Duration::ZERO); + // SAFETY: valid epoll descriptor, one event output slot, zero timeout, + // and null signal mask. The Linux kernel sigset ABI is eight bytes. + let probe = unsafe { + libc::syscall( + libc::SYS_epoll_pwait2, + fd.as_raw_fd(), + &mut event, + 1, + &zero, + std::ptr::null::(), + 8usize, + ) + }; + if probe < 0 { + let e = std::io::Error::last_os_error(); + if e.raw_os_error() != Some(libc::ENOSYS) { + return Err(e.into()); + } + true + } else { + false } - true - } else { - false }; let timer = if unsupported || cfg!(feature = "epoll-timerfd") { // SAFETY: CLOCK_MONOTONIC and timerfd flags are valid.