Rollup merge of #142391 - LevitatingBusinessMan:setsid, r=workingjubilee

rust: library: Add `setsid` method to `CommandExt` trait

Add a setsid method to the CommandExt trait so that callers can create a process in a new session and process group whilst still using the POSIX spawn fast path.

Tracking issue: rust-lang/rust#105376

ACP: https://github.com/rust-lang/libs-team/issues/184

This PR was previously submitted by ``@HarveyHunt`` (whom I marked as Co-Author in the commit message) in rust-lang/rust#105377. However that PR went stale.

I applied the [suggestion](https://github.com/rust-lang/rust/pull/105377/files/231d19fcbfe155b2e85116865adae4253380ff1f#r1893457943) to change the function signature to `fn setsid(&mut self, setsid: bool) -> &mut Command`.
This commit is contained in:
Matthias Krüger 2025-07-11 07:35:17 +02:00 committed by GitHub
commit 2730bebbf8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 89 additions and 0 deletions

View file

@ -210,6 +210,9 @@ pub trait CommandExt: Sealed {
/// intentional difference from the underlying `chroot` system call.)
#[unstable(feature = "process_chroot", issue = "141298")]
fn chroot<P: AsRef<Path>>(&mut self, dir: P) -> &mut process::Command;
#[unstable(feature = "process_setsid", issue = "105376")]
fn setsid(&mut self, setsid: bool) -> &mut process::Command;
}
#[stable(feature = "rust1", since = "1.0.0")]
@ -260,6 +263,11 @@ impl CommandExt for process::Command {
self.as_inner_mut().chroot(dir.as_ref());
self
}
fn setsid(&mut self, setsid: bool) -> &mut process::Command {
self.as_inner_mut().setsid(setsid);
self
}
}
/// Unix-specific extensions to [`process::ExitStatus`] and

View file

@ -98,6 +98,7 @@ pub struct Command {
#[cfg(target_os = "linux")]
create_pidfd: bool,
pgroup: Option<pid_t>,
setsid: bool,
}
// passed back to std::process with the pipes connected to the child, if any
@ -185,6 +186,7 @@ impl Command {
#[cfg(target_os = "linux")]
create_pidfd: false,
pgroup: None,
setsid: false,
}
}
@ -220,6 +222,9 @@ impl Command {
self.cwd(&OsStr::new("/"));
}
}
pub fn setsid(&mut self, setsid: bool) {
self.setsid = setsid;
}
#[cfg(target_os = "linux")]
pub fn create_pidfd(&mut self, val: bool) {
@ -298,6 +303,10 @@ impl Command {
pub fn get_chroot(&self) -> Option<&CStr> {
self.chroot.as_deref()
}
#[allow(dead_code)]
pub fn get_setsid(&self) -> bool {
self.setsid
}
pub fn get_closures(&mut self) -> &mut Vec<Box<dyn FnMut() -> io::Result<()> + Send + Sync>> {
&mut self.closures

View file

@ -134,6 +134,64 @@ fn test_process_group_no_posix_spawn() {
}
}
#[test]
#[cfg_attr(
any(
// See test_process_mask
target_os = "macos",
target_arch = "arm",
target_arch = "aarch64",
target_arch = "riscv64",
),
ignore
)]
fn test_setsid_posix_spawn() {
// Spawn a cat subprocess that's just going to hang since there is no I/O.
let mut cmd = Command::new(OsStr::new("cat"));
cmd.setsid(true);
cmd.stdin(Stdio::MakePipe);
cmd.stdout(Stdio::MakePipe);
let (mut cat, _pipes) = t!(cmd.spawn(Stdio::Null, true));
unsafe {
// Setsid will create a new session and process group, so check that
// we can kill the process group, which means there *is* one.
t!(cvt(libc::kill(-(cat.id() as libc::pid_t), libc::SIGINT)));
t!(cat.wait());
}
}
#[test]
#[cfg_attr(
any(
// See test_process_mask
target_os = "macos",
target_arch = "arm",
target_arch = "aarch64",
target_arch = "riscv64",
),
ignore
)]
fn test_setsid_no_posix_spawn() {
let mut cmd = Command::new(OsStr::new("cat"));
cmd.setsid(true);
cmd.stdin(Stdio::MakePipe);
cmd.stdout(Stdio::MakePipe);
unsafe {
// Same as above, create hang-y cat. This time, force using the non-posix_spawn path.
cmd.pre_exec(Box::new(|| Ok(()))); // pre_exec forces fork + exec rather than posix spawn.
let (mut cat, _pipes) = t!(cmd.spawn(Stdio::Null, true));
// Setsid will create a new session and process group, so check that
// we can kill the process group, which means there *is* one.
t!(cvt(libc::kill(-(cat.id() as libc::pid_t), libc::SIGINT)));
t!(cat.wait());
}
}
#[test]
fn test_program_kind() {
let vectors = &[

View file

@ -340,6 +340,10 @@ impl Command {
cvt(libc::setpgid(0, pgroup))?;
}
if self.get_setsid() {
cvt(libc::setsid())?;
}
// emscripten has no signal support.
#[cfg(not(target_os = "emscripten"))]
{
@ -741,6 +745,16 @@ impl Command {
flags |= libc::POSIX_SPAWN_SETSIGDEF;
}
if self.get_setsid() {
cfg_if::cfg_if! {
if #[cfg(all(target_os = "linux", target_env = "gnu"))] {
flags |= libc::POSIX_SPAWN_SETSID;
} else {
return Ok(None);
}
}
}
cvt_nz(libc::posix_spawnattr_setflags(attrs.0.as_mut_ptr(), flags as _))?;
// Make sure we synchronize access to the global `environ` resource