use conjure_error::Error;
use conjure_object::Uuid;
use crash_handler::CrashHandler;
use minidumper::{LoopAction, MinidumpBinary, ServerHandler, SocketName};
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
use std::{env, fs, io, mem, thread};
use witchcraft_log::{debug, error};
pub mod log;
const SOCKET_FILE: &str = "minidump.sock";
pub async fn init(socket_dir: &Path) -> Result<(), Error> {
log_dumps().await?;
fs::create_dir_all(socket_dir).map_err(Error::internal_safe)?;
let socket_addr = socket_dir.join(SOCKET_FILE);
let exe = env::current_exe().map_err(Error::internal_safe)?;
let child = Command::new(exe)
.arg("minidump")
.arg(&socket_addr)
.stdin(Stdio::piped())
.spawn()
.map_err(Error::internal_safe)?;
let client = connect(socket_dir).await?;
#[cfg(target_os = "linux")]
handle_restricted_ptrace(child.id())?;
let guard = CrashHandler::attach(unsafe {
crash_handler::make_crash_event(move |context| {
let _ = client.request_dump(context);
crash_handler::CrashEventResult::Handled(true)
})
})
.map_err(Error::internal_safe)?;
mem::forget(guard);
mem::forget(child.stdin);
Ok(())
}
#[cfg(target_os = "linux")]
fn handle_restricted_ptrace(child: u32) -> Result<(), Error> {
let ptrace_scope = match fs::read_to_string("/proc/sys/kernel/yama/ptrace_scope") {
Ok(buf) => buf,
Err(e) => {
debug!("error reading ptrace_scope, assuming yama not enabled", error: Error::internal_safe(e));
return Ok(());
}
};
if ptrace_scope.trim() != "1" {
debug!(
"ptrace scope not restricted, skipping PR_SET_PTRACER",
safe: {
scope: ptrace_scope
}
);
return Ok(());
}
let ret = unsafe { libc::prctl(libc::PR_SET_PTRACER, child as libc::c_ulong) };
if ret != 0 {
return Err(Error::internal_safe(io::Error::last_os_error()));
}
Ok(())
}
pub async fn connect(socket_dir: &Path) -> Result<minidumper::Client, Error> {
let socket_addr = socket_dir.join(SOCKET_FILE);
for _ in 0..200 {
let result = tokio::task::spawn_blocking({
let socket_addr = socket_addr.clone();
move || minidumper::Client::with_name(SocketName::Path(&socket_addr))
})
.await
.unwrap();
match result {
Ok(client) => return Ok(client),
Err(e) => debug!(
"error opening minidump client",
error: Error::internal_safe(e)
),
}
tokio::time::sleep(Duration::from_millis(25)).await;
}
Err(Error::internal_safe("unable to connect to minidump server"))
}
pub fn server(socket_addr: &Path) -> Result<(), Error> {
let shutdown = Arc::new(AtomicBool::new(false));
thread::spawn({
let shutdown = shutdown.clone();
move || {
let _ = io::stdin().read(&mut [0]);
shutdown.store(true, Ordering::Relaxed);
}
});
minidumper::Server::with_name(SocketName::Path(socket_addr))
.map_err(Error::internal_safe)?
.run(Box::new(WitchcraftServerHandler), &shutdown, None)
.map_err(Error::internal_safe)
}
struct WitchcraftServerHandler;
impl ServerHandler for WitchcraftServerHandler {
fn create_minidump_file(&self) -> io::Result<(File, PathBuf)> {
let dir = Path::new("var/log");
fs::create_dir_all(dir)?;
let path = dir.join(format!("{}.dmp.new", Uuid::new_v4()));
let file = File::create(&path)?;
Ok((file, path))
}
fn on_minidump_created(
&self,
_result: Result<MinidumpBinary, minidumper::Error>,
) -> LoopAction {
LoopAction::Continue
}
fn on_message(&self, _kind: u32, _buffer: Vec<u8>) {
#[cfg(target_os = "linux")]
crate::debug::thread_dump::handle_request(_buffer);
}
}
pub async fn log_dumps() -> Result<(), Error> {
let mut dir = tokio::fs::read_dir("var/log")
.await
.map_err(Error::internal_safe)?;
while let Some(entry) = dir.next_entry().await.map_err(Error::internal_safe)? {
let path = entry.path();
let file_name = match path.file_name().and_then(|n| n.to_str()) {
Some(s) => s,
None => continue,
};
if !file_name.ends_with(".dmp.new") {
continue;
}
let new_file_name = file_name.strip_suffix(".new").unwrap();
let new_path = path.with_file_name(new_file_name);
tokio::fs::rename(&path, &new_path)
.await
.map_err(Error::internal_safe)?;
if let Err(e) = log::log_minidump(&new_path).await {
error!("error logging minidump", safe: { path: new_path.to_string_lossy() }, error: e);
}
}
Ok(())
}