use std::os::fd::AsRawFd;
use nix::fcntl::{OFlag, open};
use nix::sys::signal::{SigHandler, Signal, signal};
use nix::sys::stat::Mode;
use nix::unistd::{ForkResult, chdir, close, dup2, fork, setsid};
use tokio::sync::oneshot;
use crate::procserv::error::{ProcServError, ProcServResult};
pub fn fork_and_go() -> ProcServResult<()> {
match unsafe { fork() }.map_err(|e| ProcServError::Forkpty(format!("first fork: {e}")))? {
ForkResult::Parent { .. } => {
std::process::exit(0);
}
ForkResult::Child => {}
}
setsid().map_err(|e| ProcServError::Forkpty(format!("setsid: {e}")))?;
match unsafe { fork() }.map_err(|e| ProcServError::Forkpty(format!("second fork: {e}")))? {
ForkResult::Parent { .. } => {
std::process::exit(0);
}
ForkResult::Child => {}
}
chdir("/").map_err(|e| ProcServError::Forkpty(format!("chdir(/): {e}")))?;
let null = open("/dev/null", OFlag::O_RDWR, Mode::empty())
.map_err(|e| ProcServError::Forkpty(format!("open /dev/null: {e}")))?;
let null_fd = null.as_raw_fd();
for fd in [0, 1, 2] {
dup2(null_fd, fd)
.map_err(|e| ProcServError::Forkpty(format!("dup2(/dev/null, {fd}): {e}")))?;
}
if null_fd > 2 {
let _ = close(null_fd);
}
Ok(())
}
pub async fn install_signal_handlers() -> ProcServResult<ShutdownSignal> {
unsafe {
signal(Signal::SIGPIPE, SigHandler::SigIgn)
.map_err(|e| ProcServError::Forkpty(format!("ignore SIGPIPE: {e}")))?;
}
let mut term = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
.map_err(ProcServError::Io)?;
let mut intr = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::interrupt())
.map_err(ProcServError::Io)?;
let (tx, rx) = oneshot::channel::<ShutdownReason>();
tokio::spawn(async move {
let reason = tokio::select! {
_ = term.recv() => ShutdownReason::Terminate,
_ = intr.recv() => ShutdownReason::Interrupt,
};
let _ = tx.send(reason);
});
Ok(ShutdownSignal { rx })
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ShutdownReason {
Terminate,
Interrupt,
}
pub struct ShutdownSignal {
rx: oneshot::Receiver<ShutdownReason>,
}
impl ShutdownSignal {
pub async fn wait(self) -> ProcServResult<ShutdownReason> {
self.rx.await.map_err(|_| ProcServError::Shutdown)
}
}
#[cfg(test)]
mod tests {
}