use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
use std::process;
#[derive(serde::Deserialize)]
struct Sidecar {
command: String,
#[serde(default)]
args: Vec<String>,
cwd: Option<String>,
env: Option<HashMap<String, String>>,
}
fn sidecar_path(exe: &Path) -> PathBuf {
let stem = exe
.file_stem()
.expect("daemon-trampoline: cannot determine exe file stem");
exe.with_file_name(format!("{}.daemon.json", stem.to_string_lossy()))
}
#[allow(clippy::needless_return)]
fn set_process_name(exe: &Path) {
let stem = exe
.file_stem()
.map(|s| s.to_string_lossy().into_owned())
.unwrap_or_default();
if stem.is_empty() {
return;
}
#[cfg(target_os = "linux")]
{
let truncated: String = stem.chars().take(15).collect();
let c_name = std::ffi::CString::new(truncated).unwrap_or_default();
unsafe {
libc::prctl(libc::PR_SET_NAME, c_name.as_ptr() as libc::c_ulong, 0, 0, 0);
}
}
#[cfg(target_os = "macos")]
{
let c_name = std::ffi::CString::new(stem).unwrap_or_default();
unsafe {
libc::pthread_setname_np(c_name.as_ptr());
}
}
}
fn run() -> i32 {
let exe = match std::env::current_exe() {
Ok(p) => p,
Err(e) => {
eprintln!("daemon-trampoline: failed to get current exe path: {e}");
return 1;
}
};
let sidecar = sidecar_path(&exe);
let json = match fs::read_to_string(&sidecar) {
Ok(s) => s,
Err(e) => {
eprintln!(
"daemon-trampoline: failed to read sidecar {}: {e}",
sidecar.display()
);
return 1;
}
};
let cfg: Sidecar = match serde_json::from_str(&json) {
Ok(c) => c,
Err(e) => {
eprintln!(
"daemon-trampoline: failed to parse sidecar {}: {e}",
sidecar.display()
);
return 1;
}
};
set_process_name(&exe);
let mut cmd = process::Command::new(&cfg.command);
cmd.args(&cfg.args);
if let Some(ref env) = cfg.env {
cmd.env_clear();
cmd.envs(env);
}
if let Some(ref cwd) = cfg.cwd {
cmd.current_dir(cwd);
}
#[cfg(windows)]
{
use std::os::windows::process::CommandExt;
const CREATE_NO_WINDOW: u32 = 0x0800_0000;
cmd.creation_flags(CREATE_NO_WINDOW);
}
match cmd.status() {
Ok(status) => {
#[cfg(unix)]
{
use std::os::unix::process::ExitStatusExt;
if let Some(sig) = status.signal() {
return 128 + sig;
}
}
status.code().unwrap_or(1)
}
Err(e) => {
eprintln!("daemon-trampoline: failed to spawn '{}': {e}", cfg.command);
1
}
}
}
fn main() {
process::exit(run());
}