Skip to content

Commit 231d19f

Browse files
committed
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: #105376
1 parent b22c152 commit 231d19f

File tree

4 files changed

+87
-0
lines changed

4 files changed

+87
-0
lines changed

library/std/src/os/unix/process.rs

+8
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,9 @@ pub trait CommandExt: Sealed {
179179
/// ```
180180
#[stable(feature = "process_set_process_group", since = "1.64.0")]
181181
fn process_group(&mut self, pgroup: i32) -> &mut process::Command;
182+
183+
#[unstable(feature = "process_setsid", issue = "105376")]
184+
fn setsid(&mut self) -> &mut process::Command;
182185
}
183186

184187
#[stable(feature = "rust1", since = "1.0.0")]
@@ -224,6 +227,11 @@ impl CommandExt for process::Command {
224227
self.as_inner_mut().pgroup(pgroup);
225228
self
226229
}
230+
231+
fn setsid(&mut self) -> &mut process::Command {
232+
self.as_inner_mut().setsid();
233+
self
234+
}
227235
}
228236

229237
/// Unix-specific extensions to [`process::ExitStatus`] and

library/std/src/sys/unix/process/process_common.rs

+12
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ pub struct Command {
107107
#[cfg(target_os = "linux")]
108108
create_pidfd: bool,
109109
pgroup: Option<pid_t>,
110+
setsid: bool,
110111
}
111112

112113
// Create a new type for argv, so that we can make it `Send` and `Sync`
@@ -197,6 +198,7 @@ impl Command {
197198
stdout: None,
198199
stderr: None,
199200
pgroup: None,
201+
setsid: false,
200202
}
201203
}
202204

@@ -222,6 +224,7 @@ impl Command {
222224
stderr: None,
223225
create_pidfd: false,
224226
pgroup: None,
227+
setsid: false,
225228
}
226229
}
227230

@@ -261,6 +264,10 @@ impl Command {
261264
self.pgroup = Some(pgroup);
262265
}
263266

267+
pub fn setsid(&mut self) {
268+
self.setsid = true;
269+
}
270+
264271
#[cfg(target_os = "linux")]
265272
pub fn create_pidfd(&mut self, val: bool) {
266273
self.create_pidfd = val;
@@ -333,6 +340,11 @@ impl Command {
333340
self.pgroup
334341
}
335342

343+
#[allow(dead_code)]
344+
pub fn get_setsid(&self) -> bool {
345+
self.setsid
346+
}
347+
336348
pub fn get_closures(&mut self) -> &mut Vec<Box<dyn FnMut() -> io::Result<()> + Send + Sync>> {
337349
&mut self.closures
338350
}

library/std/src/sys/unix/process/process_common/tests.rs

+58
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,64 @@ fn test_process_group_no_posix_spawn() {
136136
}
137137
}
138138

139+
#[test]
140+
#[cfg_attr(
141+
any(
142+
// See test_process_mask
143+
target_os = "macos",
144+
target_arch = "arm",
145+
target_arch = "aarch64",
146+
target_arch = "riscv64",
147+
),
148+
ignore
149+
)]
150+
fn test_setsid_posix_spawn() {
151+
// Spawn a cat subprocess that's just going to hang since there is no I/O.
152+
let mut cmd = Command::new(OsStr::new("cat"));
153+
cmd.setsid();
154+
cmd.stdin(Stdio::MakePipe);
155+
cmd.stdout(Stdio::MakePipe);
156+
let (mut cat, _pipes) = t!(cmd.spawn(Stdio::Null, true));
157+
158+
unsafe {
159+
// Setsid will create a new session and process group, so check that
160+
// we can kill the process group, which means there *is* one.
161+
t!(cvt(libc::kill(-(cat.id() as libc::pid_t), libc::SIGINT)));
162+
163+
t!(cat.wait());
164+
}
165+
}
166+
167+
#[test]
168+
#[cfg_attr(
169+
any(
170+
// See test_process_mask
171+
target_os = "macos",
172+
target_arch = "arm",
173+
target_arch = "aarch64",
174+
target_arch = "riscv64",
175+
),
176+
ignore
177+
)]
178+
fn test_setsid_no_posix_spawn() {
179+
let mut cmd = Command::new(OsStr::new("cat"));
180+
cmd.setsid();
181+
cmd.stdin(Stdio::MakePipe);
182+
cmd.stdout(Stdio::MakePipe);
183+
184+
unsafe {
185+
// Same as above, create hang-y cat. This time, force using the non-posix_spawn path.
186+
cmd.pre_exec(Box::new(|| Ok(()))); // pre_exec forces fork + exec rather than posix spawn.
187+
let (mut cat, _pipes) = t!(cmd.spawn(Stdio::Null, true));
188+
189+
// Setsid will create a new session and process group, so check that
190+
// we can kill the process group, which means there *is* one.
191+
t!(cvt(libc::kill(-(cat.id() as libc::pid_t), libc::SIGINT)));
192+
193+
t!(cat.wait());
194+
}
195+
}
196+
139197
#[test]
140198
fn test_program_kind() {
141199
let vectors = &[

library/std/src/sys/unix/process/process_unix.rs

+9
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,10 @@ impl Command {
328328
cvt(libc::setpgid(0, pgroup))?;
329329
}
330330

331+
if self.get_setsid() {
332+
cvt(libc::setsid())?;
333+
}
334+
331335
// emscripten has no signal support.
332336
#[cfg(not(target_os = "emscripten"))]
333337
{
@@ -467,6 +471,7 @@ impl Command {
467471
};
468472

469473
let pgroup = self.get_pgroup();
474+
let setsid = self.get_setsid();
470475

471476
// Safety: -1 indicates we don't have a pidfd.
472477
let mut p = unsafe { Process::new(0, -1) };
@@ -550,6 +555,10 @@ impl Command {
550555
flags |= libc::POSIX_SPAWN_SETSIGDEF;
551556
}
552557

558+
if setsid {
559+
flags |= libc::POSIX_SPAWN_SETSID;
560+
}
561+
553562
cvt_nz(libc::posix_spawnattr_setflags(attrs.0.as_mut_ptr(), flags as _))?;
554563

555564
// Make sure we synchronize access to the global `environ` resource

0 commit comments

Comments
 (0)