Skip to main content

linuxutils_system/
setsid.rs

1use linuxutils_common::man::ManContent;
2
3pub const MAN: ManContent = ManContent::empty();
4
5use clap::Parser;
6use rustix::process::{
7    Pid, WaitOptions, getpgrp, getpid, setsid as do_setsid, waitpid,
8};
9use std::{ffi::CString, os::unix::io::AsRawFd, process::ExitCode};
10
11#[derive(Parser)]
12#[command(name = "setsid", version, about = "Run a program in a new session")]
13pub struct Args {
14    /// Set the controlling terminal to the current one
15    #[arg(short = 'c', long = "ctty")]
16    ctty: bool,
17
18    /// Always create a new process
19    #[arg(short = 'f', long = "fork")]
20    fork: bool,
21
22    /// Wait for the program to end and return its exit status
23    #[arg(short = 'w', long = "wait")]
24    wait: bool,
25
26    /// Program and its arguments
27    #[arg(
28        trailing_var_arg = true,
29        allow_hyphen_values = true,
30        required = true
31    )]
32    pub command: Vec<String>,
33}
34
35pub fn run(args: Args) -> ExitCode {
36    let need_fork = args.fork || is_process_group_leader();
37
38    if need_fork {
39        // fork() is not provided by rustix because it is inherently unsafe.
40        let pid = unsafe { libc::fork() };
41        match pid {
42            -1 => {
43                eprintln!("setsid: fork: {}", std::io::Error::last_os_error());
44                return ExitCode::FAILURE;
45            }
46            0 => {
47                // Child: fall through to setsid + exec.
48            }
49            child_pid => {
50                // Parent.
51                if !args.wait {
52                    return ExitCode::SUCCESS;
53                }
54                return wait_for_child(child_pid);
55            }
56        }
57    }
58
59    if let Err(e) = do_setsid() {
60        eprintln!("setsid: setsid: {e}");
61        return ExitCode::FAILURE;
62    }
63
64    if args.ctty {
65        let stdin = std::io::stdin();
66        let fd = stdin.as_raw_fd();
67        // TIOCSCTTY: set controlling terminal.
68        let ret = unsafe { libc::ioctl(fd, libc::TIOCSCTTY, 0) };
69        if ret < 0 {
70            eprintln!(
71                "setsid: ioctl TIOCSCTTY: {}",
72                std::io::Error::last_os_error()
73            );
74            return ExitCode::FAILURE;
75        }
76    }
77
78    let program = &args.command[0];
79    let arguments = &args.command[1..];
80    exec_program(program, arguments)
81}
82
83fn is_process_group_leader() -> bool {
84    let pid = getpid();
85    let pgrp = getpgrp();
86    pid.as_raw_nonzero() == pgrp.as_raw_nonzero()
87}
88
89fn wait_for_child(pid: i32) -> ExitCode {
90    let pid = Pid::from_raw(pid);
91    let Some(pid) = pid else {
92        return ExitCode::FAILURE;
93    };
94    loop {
95        match waitpid(Some(pid), WaitOptions::empty()) {
96            Ok(Some((_pid, status))) => {
97                if let Some(code) = status.exit_status() {
98                    return ExitCode::from(code as u8);
99                }
100                if status.terminating_signal().is_some() {
101                    return ExitCode::FAILURE;
102                }
103                // Stopped/continued — keep waiting.
104            }
105            Ok(None) => continue,
106            Err(e) => {
107                eprintln!("setsid: waitpid: {e}");
108                return ExitCode::FAILURE;
109            }
110        }
111    }
112}
113
114fn exec_program(program: &str, arguments: &[String]) -> ExitCode {
115    let c_program = match CString::new(program.as_bytes()) {
116        Ok(s) => s,
117        Err(e) => {
118            eprintln!("setsid: {e}");
119            return ExitCode::FAILURE;
120        }
121    };
122
123    let mut c_args: Vec<CString> = vec![c_program.clone()];
124    for arg in arguments {
125        match CString::new(arg.as_bytes()) {
126            Ok(s) => c_args.push(s),
127            Err(e) => {
128                eprintln!("setsid: {e}");
129                return ExitCode::FAILURE;
130            }
131        }
132    }
133
134    let c_ptrs: Vec<*const libc::c_char> = c_args
135        .iter()
136        .map(|s| s.as_ptr())
137        .chain(std::iter::once(std::ptr::null()))
138        .collect();
139
140    unsafe {
141        libc::execvp(c_program.as_ptr(), c_ptrs.as_ptr());
142    }
143
144    // execvp only returns on error.
145    eprintln!(
146        "setsid: exec {program}: {}",
147        std::io::Error::last_os_error()
148    );
149    ExitCode::from(127)
150}