use std::path::Path;
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct TrapFd {
fd: Option<i32>,
}
impl TrapFd {
pub(crate) fn from_fd(fd: Option<i32>) -> Self {
Self { fd }
}
pub(crate) fn is_enabled(self) -> bool {
self.fd.is_some()
}
pub(crate) fn close(self) {
let Some(fd) = self.fd else {
return;
};
close_trap_fd(fd);
}
pub(crate) fn emit_filesystem_denial(self, operation: &str, path: &Path, mechanism: &str) {
let Some(fd) = self.fd else {
return;
};
let json = serde_json::json!({
"reason": "AccessDenied",
"type": "filesystem",
"file": path.display().to_string(),
"operation": operation,
"mechanism": mechanism
});
let response = format!("{json}\n");
write_trap_line(fd, response.as_bytes());
}
}
#[cfg(unix)]
fn write_trap_line(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)
);
}
}
#[cfg(not(unix))]
fn write_trap_line(_fd: i32, _line: &[u8]) {}
#[cfg(not(unix))]
fn close_trap_fd(_fd: i32) {}