about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMatthias Krüger <476013+matthiaskrgr@users.noreply.github.com>2025-07-11 07:35:17 +0200
committerGitHub <noreply@github.com>2025-07-11 07:35:17 +0200
commit2730bebbf88ae9473b339bb7f66eb77fb2c9fa6f (patch)
treeafb134a4eb5d5a8289e22a79c3bc9c26b7fd28dc
parentcdac44e608c3df9a241e0a1b53b3f62af250dbf1 (diff)
parente0f76871eddd0f1345eba7d09e5ab4812f8aa91d (diff)
downloadrust-2730bebbf88ae9473b339bb7f66eb77fb2c9fa6f.tar.gz
rust-2730bebbf88ae9473b339bb7f66eb77fb2c9fa6f.zip
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`.
-rw-r--r--library/std/src/os/unix/process.rs8
-rw-r--r--library/std/src/sys/process/unix/common.rs9
-rw-r--r--library/std/src/sys/process/unix/common/tests.rs58
-rw-r--r--library/std/src/sys/process/unix/unix.rs14
4 files changed, 89 insertions, 0 deletions
diff --git a/library/std/src/os/unix/process.rs b/library/std/src/os/unix/process.rs
index 466b134d8fa..76e63a69e45 100644
--- a/library/std/src/os/unix/process.rs
+++ b/library/std/src/os/unix/process.rs
@@ -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
diff --git a/library/std/src/sys/process/unix/common.rs b/library/std/src/sys/process/unix/common.rs
index b6777b76668..6219be60caf 100644
--- a/library/std/src/sys/process/unix/common.rs
+++ b/library/std/src/sys/process/unix/common.rs
@@ -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
diff --git a/library/std/src/sys/process/unix/common/tests.rs b/library/std/src/sys/process/unix/common/tests.rs
index e5c8dd6e341..5f71bf051f8 100644
--- a/library/std/src/sys/process/unix/common/tests.rs
+++ b/library/std/src/sys/process/unix/common/tests.rs
@@ -135,6 +135,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 = &[
         ("foo", ProgramKind::PathLookup),
diff --git a/library/std/src/sys/process/unix/unix.rs b/library/std/src/sys/process/unix/unix.rs
index bbd03e2b0c4..5d13d6da185 100644
--- a/library/std/src/sys/process/unix/unix.rs
+++ b/library/std/src/sys/process/unix/unix.rs
@@ -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