use linuxutils_common::man::ManContent;
pub const MAN: ManContent = ManContent::empty();
use clap::Parser;
use rustix::process::{
Pid, WaitOptions, getpgrp, getpid, setsid as do_setsid, waitpid,
};
use std::{ffi::CString, os::unix::io::AsRawFd, process::ExitCode};
#[derive(Parser)]
#[command(name = "setsid", version, about = "Run a program in a new session")]
pub struct Args {
#[arg(short = 'c', long = "ctty")]
ctty: bool,
#[arg(short = 'f', long = "fork")]
fork: bool,
#[arg(short = 'w', long = "wait")]
wait: bool,
#[arg(
trailing_var_arg = true,
allow_hyphen_values = true,
required = true
)]
pub command: Vec<String>,
}
pub fn run(args: Args) -> ExitCode {
let need_fork = args.fork || is_process_group_leader();
if need_fork {
let pid = unsafe { libc::fork() };
match pid {
-1 => {
eprintln!("setsid: fork: {}", std::io::Error::last_os_error());
return ExitCode::FAILURE;
}
0 => {
}
child_pid => {
if !args.wait {
return ExitCode::SUCCESS;
}
return wait_for_child(child_pid);
}
}
}
if let Err(e) = do_setsid() {
eprintln!("setsid: setsid: {e}");
return ExitCode::FAILURE;
}
if args.ctty {
let stdin = std::io::stdin();
let fd = stdin.as_raw_fd();
let ret = unsafe { libc::ioctl(fd, libc::TIOCSCTTY, 0) };
if ret < 0 {
eprintln!(
"setsid: ioctl TIOCSCTTY: {}",
std::io::Error::last_os_error()
);
return ExitCode::FAILURE;
}
}
let program = &args.command[0];
let arguments = &args.command[1..];
exec_program(program, arguments)
}
fn is_process_group_leader() -> bool {
let pid = getpid();
let pgrp = getpgrp();
pid.as_raw_nonzero() == pgrp.as_raw_nonzero()
}
fn wait_for_child(pid: i32) -> ExitCode {
let pid = Pid::from_raw(pid);
let Some(pid) = pid else {
return ExitCode::FAILURE;
};
loop {
match waitpid(Some(pid), WaitOptions::empty()) {
Ok(Some((_pid, status))) => {
if let Some(code) = status.exit_status() {
return ExitCode::from(code as u8);
}
if status.terminating_signal().is_some() {
return ExitCode::FAILURE;
}
}
Ok(None) => continue,
Err(e) => {
eprintln!("setsid: waitpid: {e}");
return ExitCode::FAILURE;
}
}
}
}
fn exec_program(program: &str, arguments: &[String]) -> ExitCode {
let c_program = match CString::new(program.as_bytes()) {
Ok(s) => s,
Err(e) => {
eprintln!("setsid: {e}");
return ExitCode::FAILURE;
}
};
let mut c_args: Vec<CString> = vec![c_program.clone()];
for arg in arguments {
match CString::new(arg.as_bytes()) {
Ok(s) => c_args.push(s),
Err(e) => {
eprintln!("setsid: {e}");
return ExitCode::FAILURE;
}
}
}
let c_ptrs: Vec<*const libc::c_char> = c_args
.iter()
.map(|s| s.as_ptr())
.chain(std::iter::once(std::ptr::null()))
.collect();
unsafe {
libc::execvp(c_program.as_ptr(), c_ptrs.as_ptr());
}
eprintln!(
"setsid: exec {program}: {}",
std::io::Error::last_os_error()
);
ExitCode::from(127)
}