|
6 | 6 | // spell-checker:ignore (ToDO) tempdir dyld dylib optgrps libstdbuf |
7 | 7 |
|
8 | 8 | use clap::{Arg, ArgAction, ArgMatches, Command}; |
| 9 | +use std::env; |
| 10 | +#[cfg(unix)] |
| 11 | +use std::ffi::CString; |
9 | 12 | use std::ffi::OsString; |
| 13 | +#[cfg(unix)] |
| 14 | +use std::os::unix::ffi::OsStrExt; |
10 | 15 | use std::path::PathBuf; |
11 | | -use std::process; |
12 | 16 | use tempfile::TempDir; |
13 | 17 | use tempfile::tempdir; |
14 | 18 | use thiserror::Error; |
15 | | -use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; |
| 19 | +use uucore::error::{UResult, USimpleError, UUsageError}; |
16 | 20 | use uucore::format_usage; |
17 | 21 | use uucore::parser::parse_size::parse_size_u64; |
18 | 22 | use uucore::translate; |
@@ -133,13 +137,13 @@ fn check_option(matches: &ArgMatches, name: &str) -> Result<BufferType, ProgramO |
133 | 137 | } |
134 | 138 | } |
135 | 139 |
|
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) { |
137 | 141 | match buffer_type { |
138 | 142 | BufferType::Size(m) => { |
139 | | - command.env(buffer_name, m.to_string()); |
| 143 | + unsafe { env::set_var(buffer_name, m.to_string()) }; |
140 | 144 | } |
141 | 145 | BufferType::Line => { |
142 | | - command.env(buffer_name, "L"); |
| 146 | + unsafe { env::set_var(buffer_name, "L") }; |
143 | 147 | } |
144 | 148 | BufferType::Default => {} |
145 | 149 | } |
@@ -193,69 +197,79 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { |
193 | 197 | let Some(first_command) = command_values.next() else { |
194 | 198 | return Err(UUsageError::new(125, "no command specified".to_string())); |
195 | 199 | }; |
196 | | - let mut command = process::Command::new(first_command); |
197 | 200 | let command_params: Vec<&OsString> = command_values.collect(); |
198 | 201 |
|
199 | 202 | let tmp_dir = tempdir() |
200 | 203 | .map_err(|e| UUsageError::new(125, format!("failed to create temp directory: {e}")))?; |
201 | 204 | 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; |
227 | 215 |
|
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 | + } |
235 | 245 | } |
236 | 246 | } |
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"), |
257 | 262 | } |
258 | 263 | } |
| 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 | + } |
259 | 273 | } |
260 | 274 |
|
261 | 275 | pub fn uu_app() -> Command { |
|
0 commit comments