#[cfg(target_os = "linux")]
use crate::trap::Trap;
#[derive(Clone, Debug, Default)]
pub(crate) struct TrapFd {
#[cfg_attr(not(unix), allow(dead_code))]
fd: Option<i32>,
}
impl TrapFd {
pub(crate) fn from_fd(fd: Option<i32>) -> Self {
Self { fd }
}
#[cfg(target_os = "linux")]
pub(crate) fn is_enabled(&self) -> bool {
self.fd.is_some()
}
#[cfg(target_os = "linux")]
pub(crate) fn is_socket(&self) -> bool {
self.fd.is_some_and(|fd| {
crate::platform::fd::getsockopt_int(fd, libc::SOL_SOCKET, libc::SO_TYPE).is_ok()
})
}
#[cfg(unix)]
pub(crate) fn close(&self) {
if let Some(fd) = self.fd {
close_trap_fd(fd);
}
}
#[cfg(target_os = "linux")]
pub(crate) fn fd(&self) -> Option<i32> {
self.fd
}
#[cfg(target_os = "linux")]
pub(crate) fn write(&self, trap: &Trap) {
let Ok(line) = serde_json::to_string(trap) else {
return;
};
let line = format!("{line}\n");
if let Some(fd) = self.fd {
write_trap_fd(fd, line.as_bytes());
}
}
}
#[cfg(target_os = "linux")]
fn write_trap_fd(fd: i32, line: &[u8]) {
let mut remaining = line;
while !remaining.is_empty() {
let written = unsafe { libc::write(fd, remaining.as_ptr().cast(), remaining.len()) };
if written == 0 {
return;
}
if written < 0 {
let error = std::io::Error::last_os_error();
if error.raw_os_error() == Some(libc::EINTR) {
continue;
}
log::debug!(
"trap fd write fd={fd} errno={}",
error.raw_os_error().unwrap_or(0)
);
return;
}
let Ok(written) = usize::try_from(written) else {
return;
};
remaining = &remaining[written..];
}
}
#[cfg(unix)]
fn close_trap_fd(fd: i32) {
let rc = unsafe { libc::close(fd) };
if rc != 0 {
let error = std::io::Error::last_os_error();
log::debug!(
"trap fd close fd={fd} errno={}",
error.raw_os_error().unwrap_or(0)
);
}
}