use chrono::Utc;
use marshal_entities::{Session, SessionId};
use serde_json::json;
use std::path::PathBuf;
pub fn write(session: &Session, session_id: &SessionId) {
let Some(ppid) = current_ppid() else { return };
let Some(dir) = state_dir() else { return };
let path = dir.join(format!("shim-by-ppid-{ppid}.json"));
if let Err(e) = std::fs::create_dir_all(&dir) {
log::debug!("[marshal-shim] state file mkdir {dir:?} failed: {e}");
return;
}
let body = json!({
"session_id": session_id.0.as_ref(),
"nickname": session.nickname,
"pid": session.pid,
"ppid": ppid,
"cwd": session.cwd,
"git_branch": session.git_branch,
"updated_at": Utc::now().timestamp_millis(),
});
let Ok(bytes) = serde_json::to_vec_pretty(&body) else {
return;
};
let tmp = path.with_extension("json.tmp");
if let Err(e) = std::fs::write(&tmp, &bytes) {
log::debug!("[marshal-shim] state file write {tmp:?} failed: {e}");
return;
}
if let Err(e) = std::fs::rename(&tmp, &path) {
log::debug!("[marshal-shim] state file rename {path:?} failed: {e}");
}
}
#[cfg(unix)]
fn current_ppid() -> Option<u32> {
Some(unsafe { libc::getppid() as u32 })
}
#[cfg(windows)]
fn current_ppid() -> Option<u32> {
use windows_sys::Win32::Foundation::{CloseHandle, INVALID_HANDLE_VALUE};
use windows_sys::Win32::System::Diagnostics::ToolHelp::{
CreateToolhelp32Snapshot, PROCESSENTRY32, Process32First, Process32Next, TH32CS_SNAPPROCESS,
};
use windows_sys::Win32::System::Threading::GetCurrentProcessId;
unsafe {
let pid = GetCurrentProcessId();
let snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if snap == INVALID_HANDLE_VALUE {
return None;
}
let mut entry: PROCESSENTRY32 = std::mem::zeroed();
entry.dwSize = std::mem::size_of::<PROCESSENTRY32>() as u32;
let mut ppid = None;
if Process32First(snap, &mut entry) != 0 {
loop {
if entry.th32ProcessID == pid {
ppid = Some(entry.th32ParentProcessID);
break;
}
if Process32Next(snap, &mut entry) == 0 {
break;
}
}
}
CloseHandle(snap);
ppid
}
}
#[cfg(not(any(unix, windows)))]
fn current_ppid() -> Option<u32> {
None
}
#[cfg(unix)]
fn state_dir() -> Option<PathBuf> {
if let Some(s) = std::env::var_os("XDG_STATE_HOME") {
let p = PathBuf::from(s);
if !p.as_os_str().is_empty() {
return Some(p.join("marshal"));
}
}
let home = std::env::var_os("HOME")?;
Some(PathBuf::from(home).join(".local/state/marshal"))
}
#[cfg(windows)]
fn state_dir() -> Option<PathBuf> {
let base = std::env::var_os("LOCALAPPDATA").or_else(|| std::env::var_os("APPDATA"))?;
Some(PathBuf::from(base).join("marshal").join("state"))
}
#[cfg(not(any(unix, windows)))]
fn state_dir() -> Option<PathBuf> {
None
}