use std::path::PathBuf;
pub use trusty_common::embedder_client::EmbedderSupervisor;
#[derive(Debug, Clone)]
pub struct SupervisorConfig {
pub startup_timeout_secs: u64,
pub backoff_max_secs: u64,
pub max_restarts: u32,
}
impl SupervisorConfig {
pub fn from_env() -> Self {
Self {
startup_timeout_secs: parse_env_u64("TRUSTY_EMBEDDERD_STARTUP_TIMEOUT_SECS", 30),
backoff_max_secs: parse_env_u64("TRUSTY_EMBEDDERD_RESTART_BACKOFF_MAX_SECS", 60),
max_restarts: parse_env_u32("TRUSTY_EMBEDDERD_MAX_RESTARTS", 5),
}
}
pub fn into_common(self) -> trusty_common::embedder_client::SupervisorConfig {
trusty_common::embedder_client::SupervisorConfig {
startup_timeout_secs: self.startup_timeout_secs,
backoff_max_secs: self.backoff_max_secs,
max_restarts: self.max_restarts,
}
}
}
impl Default for SupervisorConfig {
fn default() -> Self {
Self {
startup_timeout_secs: 30,
backoff_max_secs: 60,
max_restarts: 5,
}
}
}
pub fn locate_embedderd_binary() -> anyhow::Result<PathBuf> {
trusty_common::embedder_client::locate_embedderd_binary()
}
pub fn default_socket_path() -> PathBuf {
let pid = std::process::id();
let filename = format!("trusty-embedderd-{pid}.sock");
let dir = std::env::var("TMPDIR")
.ok()
.filter(|s| !s.trim().is_empty())
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from("/tmp"));
dir.join(filename)
}
fn parse_env_u64(var: &str, default: u64) -> u64 {
std::env::var(var)
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(default)
}
fn parse_env_u32(var: &str, default: u32) -> u32 {
std::env::var(var)
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(default)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn config_from_env_defaults() {
let _g1 = EnvGuard::remove("TRUSTY_EMBEDDERD_STARTUP_TIMEOUT_SECS");
let _g2 = EnvGuard::remove("TRUSTY_EMBEDDERD_RESTART_BACKOFF_MAX_SECS");
let _g3 = EnvGuard::remove("TRUSTY_EMBEDDERD_MAX_RESTARTS");
let cfg = SupervisorConfig::from_env();
assert_eq!(cfg.startup_timeout_secs, 30);
assert_eq!(cfg.backoff_max_secs, 60);
assert_eq!(cfg.max_restarts, 5);
}
#[test]
fn config_from_env_overrides() {
let _g1 = EnvGuard::set("TRUSTY_EMBEDDERD_STARTUP_TIMEOUT_SECS", "15");
let _g2 = EnvGuard::set("TRUSTY_EMBEDDERD_RESTART_BACKOFF_MAX_SECS", "120");
let _g3 = EnvGuard::set("TRUSTY_EMBEDDERD_MAX_RESTARTS", "10");
let cfg = SupervisorConfig::from_env();
assert_eq!(cfg.startup_timeout_secs, 15);
assert_eq!(cfg.backoff_max_secs, 120);
assert_eq!(cfg.max_restarts, 10);
}
#[test]
fn config_from_env_ignores_malformed() {
let _g1 = EnvGuard::set("TRUSTY_EMBEDDERD_STARTUP_TIMEOUT_SECS", "not_a_number");
let _g2 = EnvGuard::set("TRUSTY_EMBEDDERD_MAX_RESTARTS", "bad");
let cfg = SupervisorConfig::from_env();
assert_eq!(cfg.startup_timeout_secs, 30);
assert_eq!(cfg.max_restarts, 5);
}
#[test]
fn into_common_maps_fields() {
let cfg = SupervisorConfig {
startup_timeout_secs: 99,
backoff_max_secs: 77,
max_restarts: 3,
};
let common = cfg.into_common();
assert_eq!(common.startup_timeout_secs, 99);
assert_eq!(common.backoff_max_secs, 77);
assert_eq!(common.max_restarts, 3);
}
#[test]
fn default_socket_path_is_pid_specific() {
let p = default_socket_path();
let pid = std::process::id().to_string();
assert!(
p.to_string_lossy().contains(&pid),
"socket path {p:?} must contain PID {pid}"
);
assert_eq!(
p,
default_socket_path(),
"must be deterministic for same PID"
);
}
#[test]
fn default_socket_path_has_parent() {
let p = default_socket_path();
assert!(
p.parent().is_some_and(|pp| !pp.as_os_str().is_empty()),
"socket path {p:?} must have a non-empty parent"
);
}
#[test]
fn locate_binary_bad_explicit_path_errors() {
let _g = EnvGuard::set("TRUSTY_EMBEDDERD_BIN", "/nonexistent/path/trusty-embedderd");
let result = locate_embedderd_binary();
assert!(result.is_err(), "expected Err, got {result:?}");
}
#[test]
fn locate_binary_via_explicit_env() {
let tmp = tempfile::NamedTempFile::new().unwrap();
let path = tmp.path().to_path_buf();
let _g = EnvGuard::set("TRUSTY_EMBEDDERD_BIN", path.to_str().unwrap());
let result = locate_embedderd_binary();
assert!(result.is_ok(), "expected Ok, got {result:?}");
assert_eq!(result.unwrap(), path);
}
struct EnvGuard {
key: String,
old: Option<String>,
}
impl EnvGuard {
fn set(key: &str, value: &str) -> Self {
let old = std::env::var(key).ok();
unsafe { std::env::set_var(key, value) }
Self {
key: key.to_owned(),
old,
}
}
fn remove(key: &str) -> Self {
let old = std::env::var(key).ok();
unsafe { std::env::remove_var(key) }
Self {
key: key.to_owned(),
old,
}
}
}
impl Drop for EnvGuard {
fn drop(&mut self) {
unsafe {
match &self.old {
Some(v) => std::env::set_var(&self.key, v),
None => std::env::remove_var(&self.key),
}
}
}
}
}