Skip to content

Commit 0110357

Browse files
committed
stdbuf: use exec instead of forking
forking creates a new PID and it not compatible with GNU coreutils implementation. Fixes #9066 Signed-off-by: Etienne Cordonnier <[email protected]>
1 parent ee39b35 commit 0110357

File tree

3 files changed

+76
-58
lines changed

3 files changed

+76
-58
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/uu/stdbuf/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ uucore = { workspace = true, features = ["parser"] }
2626
thiserror = { workspace = true }
2727
fluent = { workspace = true }
2828

29+
[target.'cfg(unix)'.dependencies]
30+
nix = { workspace = true }
31+
2932
# "feat_external_libstdbuf": use an external libstdbuf.so for stdbuf instead of embedding it into
3033
# the stdbuf binary.
3134
# There are 2 use-cases:

src/uu/stdbuf/src/stdbuf.rs

Lines changed: 72 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@
66
// spell-checker:ignore (ToDO) tempdir dyld dylib optgrps libstdbuf
77

88
use clap::{Arg, ArgAction, ArgMatches, Command};
9+
use std::env;
10+
#[cfg(unix)]
11+
use std::ffi::CString;
912
use std::ffi::OsString;
13+
#[cfg(unix)]
14+
use std::os::unix::ffi::OsStrExt;
1015
use std::path::PathBuf;
11-
use std::process;
1216
use tempfile::TempDir;
1317
use tempfile::tempdir;
1418
use thiserror::Error;
15-
use uucore::error::{FromIo, UResult, USimpleError, UUsageError};
19+
use uucore::error::{UResult, USimpleError, UUsageError};
1620
use uucore::format_usage;
1721
use uucore::parser::parse_size::parse_size_u64;
1822
use uucore::translate;
@@ -133,13 +137,13 @@ fn check_option(matches: &ArgMatches, name: &str) -> Result<BufferType, ProgramO
133137
}
134138
}
135139

136-
fn set_command_env(command: &mut process::Command, buffer_name: &str, buffer_type: &BufferType) {
140+
fn set_env_var(buffer_name: &str, buffer_type: &BufferType) {
137141
match buffer_type {
138142
BufferType::Size(m) => {
139-
command.env(buffer_name, m.to_string());
143+
unsafe { env::set_var(buffer_name, m.to_string()) };
140144
}
141145
BufferType::Line => {
142-
command.env(buffer_name, "L");
146+
unsafe { env::set_var(buffer_name, "L") };
143147
}
144148
BufferType::Default => {}
145149
}
@@ -193,69 +197,79 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
193197
let Some(first_command) = command_values.next() else {
194198
return Err(UUsageError::new(125, "no command specified".to_string()));
195199
};
196-
let mut command = process::Command::new(first_command);
197200
let command_params: Vec<&OsString> = command_values.collect();
198201

199202
let tmp_dir = tempdir()
200203
.map_err(|e| UUsageError::new(125, format!("failed to create temp directory: {e}")))?;
201204
let (preload_env, libstdbuf) = get_preload_env(&tmp_dir)?;
202-
command.env(preload_env, libstdbuf);
203-
set_command_env(&mut command, "_STDBUF_I", &options.stdin);
204-
set_command_env(&mut command, "_STDBUF_O", &options.stdout);
205-
set_command_env(&mut command, "_STDBUF_E", &options.stderr);
206-
command.args(command_params);
207-
208-
let mut process = match command.spawn() {
209-
Ok(p) => p,
210-
Err(e) => {
211-
return match e.kind() {
212-
std::io::ErrorKind::PermissionDenied => Err(USimpleError::new(
213-
126,
214-
translate!("stdbuf-error-permission-denied"),
215-
)),
216-
std::io::ErrorKind::NotFound => Err(USimpleError::new(
217-
127,
218-
translate!("stdbuf-error-no-such-file"),
219-
)),
220-
_ => Err(USimpleError::new(
221-
1,
222-
translate!("stdbuf-error-failed-to-execute", "error" => e),
223-
)),
224-
};
225-
}
226-
};
205+
// Set preload and stdbuf env vars in the current process so execvp will inherit them.
206+
unsafe { env::set_var(&preload_env, &libstdbuf) };
207+
set_env_var("_STDBUF_I", &options.stdin);
208+
set_env_var("_STDBUF_O", &options.stdout);
209+
set_env_var("_STDBUF_E", &options.stderr);
210+
211+
// On Unix, replace the current process with the target program (no fork) using execvp.
212+
#[cfg(unix)]
213+
{
214+
use nix::unistd::execvp;
227215

228-
let status = process.wait().map_err_context(String::new)?;
229-
match status.code() {
230-
Some(i) => {
231-
if i == 0 {
232-
Ok(())
233-
} else {
234-
Err(i.into())
216+
// Convert program and args into CString
217+
let prog_bytes = first_command.as_os_str().as_bytes();
218+
let Ok(prog_c) = CString::new(prog_bytes) else {
219+
return Err(USimpleError::new(
220+
127,
221+
translate!("stdbuf-error-no-such-file"),
222+
));
223+
};
224+
225+
let mut argv: Vec<CString> = Vec::new();
226+
// push arg0
227+
let Ok(arg0_c) = CString::new(prog_bytes) else {
228+
return Err(USimpleError::new(
229+
127,
230+
translate!("stdbuf-error-no-such-file"),
231+
));
232+
};
233+
argv.push(arg0_c);
234+
235+
for p in &command_params {
236+
let bytes = p.as_os_str().as_bytes();
237+
match CString::new(bytes) {
238+
Ok(c) => argv.push(c),
239+
Err(_) => {
240+
return Err(USimpleError::new(
241+
127,
242+
translate!("stdbuf-error-no-such-file"),
243+
));
244+
}
235245
}
236246
}
237-
None => {
238-
#[cfg(unix)]
239-
{
240-
use std::os::unix::process::ExitStatusExt;
241-
let signal_msg = status
242-
.signal()
243-
.map(|s| s.to_string())
244-
.unwrap_or_else(|| "unknown".to_string());
245-
Err(USimpleError::new(
246-
1,
247-
translate!("stdbuf-error-killed-by-signal", "signal" => signal_msg),
248-
))
249-
}
250-
#[cfg(not(unix))]
251-
{
252-
Err(USimpleError::new(
253-
1,
254-
"process terminated abnormally".to_string(),
255-
))
256-
}
247+
248+
match execvp(&prog_c, &argv) {
249+
Err(nix::errno::Errno::ENOENT) => Err(USimpleError::new(
250+
127,
251+
translate!("stdbuf-error-no-such-file"),
252+
)),
253+
Err(nix::errno::Errno::EACCES) => Err(USimpleError::new(
254+
126,
255+
translate!("stdbuf-error-permission-denied"),
256+
)),
257+
Err(_) => Err(USimpleError::new(
258+
1,
259+
translate!("stdbuf-error-failed-to-execute", "error" => "execvp failed"),
260+
)),
261+
Ok(_) => unreachable!("execvp should not return on success"),
257262
}
258263
}
264+
265+
// On non-Unix platforms, stdbuf is not supported (it relies on LD_PRELOAD/DYLD behavior).
266+
#[cfg(not(unix))]
267+
{
268+
return Err(USimpleError::new(
269+
1,
270+
translate!("stdbuf-error-command-not-supported"),
271+
));
272+
}
259273
}
260274

261275
pub fn uu_app() -> Command {

0 commit comments

Comments
 (0)