#[cfg(unix)]
pub struct SuppressStderr {
original_fd: Option<std::os::fd::OwnedFd>,
}
#[cfg(unix)]
impl SuppressStderr {
pub fn new() -> Option<Self> {
use std::fs::File;
use std::os::fd::{AsFd, AsRawFd, FromRawFd, OwnedFd};
let stderr_fd = std::io::stderr().as_raw_fd();
let original_fd = unsafe {
let dup_fd = libc::dup(stderr_fd);
if dup_fd < 0 {
return None;
}
Some(OwnedFd::from_raw_fd(dup_fd))
};
if let Ok(devnull) = File::open("/dev/null") {
unsafe {
libc::dup2(devnull.as_fd().as_raw_fd(), stderr_fd);
}
} else {
return None;
}
Some(Self { original_fd })
}
}
#[cfg(unix)]
impl Drop for SuppressStderr {
fn drop(&mut self) {
use std::os::fd::AsRawFd;
if let Some(ref original) = self.original_fd {
let stderr_fd = std::io::stderr().as_raw_fd();
unsafe {
libc::dup2(original.as_raw_fd(), stderr_fd);
}
}
}
}
#[cfg(not(unix))]
pub struct SuppressStderr;
#[cfg(not(unix))]
impl SuppressStderr {
pub fn new() -> Option<Self> {
Some(Self)
}
}
pub fn with_suppressed_stderr<F, T>(f: F) -> T
where
F: FnOnce() -> T,
{
use std::panic::{self, AssertUnwindSafe};
use std::sync::{Arc, Mutex};
let captured: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
let captured_for_hook = Arc::clone(&captured);
let prev_hook = panic::take_hook();
panic::set_hook(Box::new(move |info| {
let mut buf = format!("{}\n", info);
let bt = std::backtrace::Backtrace::capture();
if bt.status() == std::backtrace::BacktraceStatus::Captured {
buf.push_str(&format!("stack backtrace:\n{}\n", bt));
}
if let Ok(mut slot) = captured_for_hook.lock() {
*slot = Some(buf);
}
}));
let guard = SuppressStderr::new();
let result = panic::catch_unwind(AssertUnwindSafe(f));
drop(guard);
panic::set_hook(prev_hook);
match result {
Ok(value) => value,
Err(payload) => {
if let Some(msg) = captured.lock().ok().and_then(|mut s| s.take()) {
eprint!("{}", msg);
}
panic::resume_unwind(payload);
}
}
}