Files
ElyOffice/crates/ely-files/src/crash.rs
T
ZacharyZhang-NY b7fca0146d Ely Office: six offline-first desktop apps in Rust and GPUI
ElyDoc, ElyTable, ElyShow (LibreOfficeKit), ElyPDF (MuPDF), ElyTree (Git),
ElyMark (Milkdown and CodeMirror in CEF). ElyMail and ElyAgent follow.
2026-10-08 21:20:08 -04:00

110 lines
3.4 KiB
Rust

use std::{
io,
path::{Path, PathBuf},
};
/// What the last session's end looked like (§6.2).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Marker {
Clean,
Crashed,
Discarded,
}
/// The lock that tells crash from discard.
pub struct Crash {
file: PathBuf,
}
impl Crash {
/// Marks the session open under `pid`.
pub fn open(dir: &Path, pid: u32) -> io::Result<Self> {
std::fs::create_dir_all(dir)?;
let file = dir.join("session.lock");
std::fs::write(&file, format!("open {pid}"))?;
Ok(Self { file })
}
/// Reads the lock; a dead pid means a crash (§6.2).
pub fn check(dir: &Path, alive: impl Fn(u32) -> bool) -> io::Result<Marker> {
let file = dir.join("session.lock");
let text = match std::fs::read_to_string(&file) {
Ok(text) => text,
Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(Marker::Clean),
Err(error) => return Err(error),
};
let mut parts = text.split_whitespace();
match (parts.next(), parts.next()) {
(Some("discarded"), _) => Ok(Marker::Discarded),
(Some("open"), Some(pid)) => {
let pid: u32 = pid.parse().map_err(|_| {
io::Error::new(io::ErrorKind::InvalidData, "bad pid in the session lock")
})?;
Ok(if alive(pid) {
Marker::Clean
} else {
Marker::Crashed
})
}
_ => Err(io::Error::new(
io::ErrorKind::InvalidData,
"bad session lock",
)),
}
}
/// An explicit discard: never a crash next launch (§6.2).
pub fn discard(self) -> io::Result<()> {
std::fs::write(&self.file, "discarded 0")
}
/// A clean close removes the lock.
pub fn close(self) -> io::Result<()> {
match std::fs::remove_file(&self.file) {
Ok(()) => Ok(()),
Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(()),
Err(error) => Err(error),
}
}
}
/// Whether process `pid` still runs; a crash leaves none.
#[cfg(unix)]
pub fn alive(pid: u32) -> bool {
let Ok(pid) = i32::try_from(pid) else {
return false;
};
// Another user's live process answers EPERM.
let answered = unsafe { libc::kill(pid, 0) } == 0;
answered || io::Error::last_os_error().raw_os_error() == Some(libc::EPERM)
}
/// Whether process `pid` still runs; a crash leaves none.
#[cfg(windows)]
pub fn alive(pid: u32) -> bool {
use windows_sys::Win32::Foundation::{CloseHandle, STILL_ACTIVE};
use windows_sys::Win32::System::Threading::{
GetExitCodeProcess, OpenProcess, PROCESS_QUERY_LIMITED_INFORMATION,
};
let handle = unsafe { OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, pid) };
if handle.is_null() {
return false;
}
let mut code = 0;
let read = unsafe { GetExitCodeProcess(handle, &mut code) } != 0;
unsafe { CloseHandle(handle) };
read && code == STILL_ACTIVE as u32
}
#[cfg(all(test, unix))]
mod alive_tests {
#[test]
fn this_process_lives_a_reaped_one_does_not() {
assert!(super::alive(std::process::id()));
let mut child = std::process::Command::new("true").spawn().expect("a child");
let pid = child.id();
child.wait().expect("the child ends");
assert!(!super::alive(pid));
}
}