Skip to content

Commit 0d7529b

Browse files
committed
stdbuf: add test verifying that execvp is used
Signed-off-by: Etienne Cordonnier <[email protected]>
1 parent 0110357 commit 0d7529b

File tree

1 file changed

+62
-1
lines changed

1 file changed

+62
-1
lines changed

tests/by-util/test_stdbuf.rs

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// For the full copyright and license information, please view the LICENSE
44
// file that was distributed with this source code.
5-
// spell-checker:ignore dyld dylib setvbuf
5+
// spell-checker:ignore cmdline dyld dylib PDEATHSIG setvbuf
66
#[cfg(target_os = "linux")]
77
use uutests::at_and_ucmd;
88
use uutests::new_ucmd;
@@ -247,3 +247,64 @@ fn test_stdbuf_non_utf8_paths() {
247247
.succeeds()
248248
.stdout_is("test content for stdbuf\n");
249249
}
250+
251+
#[test]
252+
#[cfg(target_os = "linux")]
253+
fn test_stdbuf_no_fork_regression() {
254+
// Regression test for issue #9066: https://github.com/uutils/coreutils/issues/9066
255+
// The original stdbuf implementation used fork+spawn which broke signal handling
256+
// and PR_SET_PDEATHSIG. This test verifies that stdbuf uses exec() instead.
257+
// With fork: stdbuf process would remain visible in process list
258+
// With exec: stdbuf process is replaced by target command (GNU compatible)
259+
260+
use std::process::{Command, Stdio};
261+
use std::thread;
262+
use std::time::Duration;
263+
264+
let scene = TestScenario::new(util_name!());
265+
266+
// Start stdbuf with a long-running command
267+
let mut child = Command::new(&scene.bin_path)
268+
.args(["stdbuf", "-o0", "sleep", "3"])
269+
.stdout(Stdio::null())
270+
.stderr(Stdio::null())
271+
.spawn()
272+
.expect("Failed to start stdbuf");
273+
274+
let child_pid = child.id();
275+
276+
// Give it a moment to exec
277+
thread::sleep(Duration::from_millis(200));
278+
279+
// Check what command is actually running at that PID using /proc
280+
let cmdline_path = format!("/proc/{child_pid}/cmdline");
281+
let cmdline_result = std::fs::read_to_string(&cmdline_path);
282+
283+
match cmdline_result {
284+
Ok(cmdline) => {
285+
// cmdline in /proc contains null-separated arguments
286+
let cmd_parts: Vec<&str> = cmdline.split('\0').collect();
287+
let command_name = cmd_parts.first().map_or("", |v| v);
288+
289+
if command_name.contains("stdbuf") {
290+
child.kill().ok();
291+
panic!(
292+
"REGRESSION: Process {child_pid} is still stdbuf - fork() used instead of exec()"
293+
);
294+
} else if command_name.contains("sleep") {
295+
println!("PASS: Process {child_pid} became 'sleep' - exec() used correctly");
296+
} else {
297+
child.kill().ok();
298+
panic!("Unexpected command at PID {child_pid}: {command_name}");
299+
}
300+
}
301+
Err(_) => {
302+
// Process may have already exited, which is also fine for this test
303+
println!("PASS: Process {child_pid} exited (likely exec worked)");
304+
}
305+
}
306+
307+
// Cleanup
308+
child.kill().ok();
309+
child.wait().ok();
310+
}

0 commit comments

Comments
 (0)