use std::path::Path;
#[derive(Clone, Debug)]
pub struct DoubleSpawnInfo {
inner: imp::DoubleSpawnInfo,
}
impl DoubleSpawnInfo {
pub const SUBCOMMAND_NAME: &'static str = "__double-spawn";
pub fn try_enable() -> Self {
Self {
inner: imp::DoubleSpawnInfo::try_enable(),
}
}
pub fn disabled() -> Self {
Self {
inner: imp::DoubleSpawnInfo::disabled(),
}
}
pub fn current_exe(&self) -> Option<&Path> {
self.inner.current_exe()
}
pub fn spawn_context(&self) -> Option<DoubleSpawnContext> {
self.current_exe().map(|_| DoubleSpawnContext::new())
}
}
#[derive(Debug)]
pub struct DoubleSpawnContext {
#[expect(dead_code)]
inner: imp::DoubleSpawnContext,
}
impl DoubleSpawnContext {
#[inline]
fn new() -> Self {
Self {
inner: imp::DoubleSpawnContext::new(),
}
}
pub fn finish(self) {}
}
pub fn double_spawn_child_init() {
imp::double_spawn_child_init()
}
#[cfg(unix)]
mod imp {
use super::*;
use nix::sys::signal::{SigSet, Signal};
use std::path::PathBuf;
use tracing::warn;
#[derive(Clone, Debug)]
pub(super) struct DoubleSpawnInfo {
current_exe: Option<PathBuf>,
}
impl DoubleSpawnInfo {
#[inline]
pub(super) fn try_enable() -> Self {
let current_exe = get_current_exe().map_or_else(
|error| {
warn!(
"unable to determine current exe, will not use double-spawning \
for better isolation: {error}"
);
None
},
Some,
);
Self { current_exe }
}
#[inline]
pub(super) fn disabled() -> Self {
Self { current_exe: None }
}
#[inline]
pub(super) fn current_exe(&self) -> Option<&Path> {
self.current_exe.as_deref()
}
}
#[cfg(target_os = "linux")]
fn get_current_exe() -> std::io::Result<PathBuf> {
static PROC_SELF_EXE: &str = "/proc/self/exe";
let path = Path::new(PROC_SELF_EXE);
match path.symlink_metadata() {
Ok(_) => Ok(path.to_owned()),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Err(std::io::Error::other(
"no /proc/self/exe available. Is /proc mounted?",
)),
Err(e) => Err(e),
}
}
#[cfg(not(target_os = "linux"))]
#[inline]
fn get_current_exe() -> std::io::Result<PathBuf> {
std::env::current_exe()
}
#[derive(Debug)]
pub(super) struct DoubleSpawnContext {
to_unblock: Option<SigSet>,
}
impl DoubleSpawnContext {
#[inline]
pub(super) fn new() -> Self {
let mut sigset = SigSet::empty();
sigset.add(Signal::SIGTSTP);
let to_unblock = sigset.thread_block().ok().map(|()| sigset);
Self { to_unblock }
}
}
impl Drop for DoubleSpawnContext {
fn drop(&mut self) {
if let Some(sigset) = &self.to_unblock {
_ = sigset.thread_unblock();
}
}
}
#[inline]
pub(super) fn double_spawn_child_init() {
let mut sigset = SigSet::empty();
sigset.add(Signal::SIGTSTP);
if sigset.thread_unblock().is_err() {
warn!("[double-spawn] unable to unblock SIGTSTP in child");
}
}
}
#[cfg(not(unix))]
mod imp {
use super::*;
#[derive(Clone, Debug)]
pub(super) struct DoubleSpawnInfo {}
impl DoubleSpawnInfo {
#[inline]
pub(super) fn try_enable() -> Self {
Self {}
}
#[inline]
pub(super) fn disabled() -> Self {
Self {}
}
#[inline]
pub(super) fn current_exe(&self) -> Option<&Path> {
None
}
}
#[derive(Debug)]
pub(super) struct DoubleSpawnContext {}
impl DoubleSpawnContext {
#[inline]
pub(super) fn new() -> Self {
Self {}
}
}
#[inline]
pub(super) fn double_spawn_child_init() {}
}