diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index b5b42c73cab..f0140392fe9 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -2154,8 +2154,8 @@ name = "uufuzz" version = "0.8.0" dependencies = [ "console", - "libc", "rand", + "rustix", "similar", "tempfile", "uucore", diff --git a/fuzz/uufuzz/Cargo.toml b/fuzz/uufuzz/Cargo.toml index 5fb706a50ac..8e74b05c3af 100644 --- a/fuzz/uufuzz/Cargo.toml +++ b/fuzz/uufuzz/Cargo.toml @@ -9,8 +9,8 @@ license.workspace = true [dependencies] console = "0.16.0" -libc = "0.2.153" rand = { version = "0.10.1", features = ["std_rng"] } similar = "3.0.0" uucore = { version = "0.8.0", path = "../../src/uucore", features = ["parser"] } tempfile = "3.15.0" +rustix = { version = "1.1.4", features = ["stdio", "pipe"] } diff --git a/fuzz/uufuzz/src/lib.rs b/fuzz/uufuzz/src/lib.rs index e7c135c3703..5f69323e790 100644 --- a/fuzz/uufuzz/src/lib.rs +++ b/fuzz/uufuzz/src/lib.rs @@ -4,18 +4,21 @@ // file that was distributed with this source code. use console::Style; -use libc::STDIN_FILENO; -use libc::{STDERR_FILENO, STDOUT_FILENO, close, dup, dup2, pipe}; use pretty_print::{ print_diff, print_end_with_status, print_or_empty, print_section, print_with_style, }; use rand::RngExt; use rand::prelude::IndexedRandom; +use rustix::io::dup; +use rustix::io::read; +use rustix::pipe::pipe; +use rustix::stdio::{dup2_stderr, dup2_stdin, dup2_stdout}; use std::env::temp_dir; use std::ffi::OsString; use std::fs::File; use std::io::{Seek, SeekFrom, Write}; -use std::os::fd::{AsRawFd, RawFd}; +use std::os::fd::{AsRawFd, OwnedFd, RawFd}; +use std::os::unix::io::FromRawFd; use std::process::{Command, Stdio}; use std::sync::atomic::Ordering; use std::sync::{Once, atomic::AtomicBool}; @@ -67,42 +70,53 @@ pub fn generate_and_run_uumain( where F: FnOnce(std::vec::IntoIter) -> i32 + Send + 'static, { - // Duplicate the stdout and stderr file descriptors - let original_stdout_fd = unsafe { dup(STDOUT_FILENO) }; - let original_stderr_fd = unsafe { dup(STDERR_FILENO) }; - if original_stdout_fd == -1 || original_stderr_fd == -1 { - return CommandResult { - stdout: "".to_string(), - stderr: "Failed to duplicate STDOUT_FILENO or STDERR_FILENO".to_string(), - exit_code: -1, - }; - } + // Duplicate the stdout and stderr file descriptors to restore later + let original_stdout_fd_owned = match dup(std::io::stdout()) { + Ok(fd) => fd, + Err(_) => { + return CommandResult { + stdout: "".to_string(), + stderr: "Failed to duplicate STDOUT_FILENO".to_string(), + exit_code: -1, + }; + } + }; - println!("Running test {:?}", &args[0..]); - let mut pipe_stdout_fds = [-1; 2]; - let mut pipe_stderr_fds = [-1; 2]; + let original_stderr_fd_owned = match dup(std::io::stderr()) { + Ok(fd) => fd, + Err(_) => { + return CommandResult { + stdout: "".to_string(), + stderr: "Failed to duplicate STDERR_FILENO".to_string(), + exit_code: -1, + }; + } + }; - // Create pipes for stdout and stderr - if unsafe { pipe(pipe_stdout_fds.as_mut_ptr()) } == -1 - || unsafe { pipe(pipe_stderr_fds.as_mut_ptr()) } == -1 - { - return CommandResult { - stdout: "".to_string(), - stderr: "Failed to create pipes".to_string(), - exit_code: -1, - }; - } + println!("Running test {:?}", &args[0..]); + let (read_pipe_stdout, write_pipe_stdout) = match pipe() { + Ok(fds) => fds, + Err(_) => { + return CommandResult { + stdout: "".to_string(), + stderr: "Failed to create pipes".to_string(), + exit_code: -1, + }; + } + }; + let (read_pipe_stderr, write_pipe_stderr) = match pipe() { + Ok(fds) => fds, + Err(_) => { + return CommandResult { + stdout: "".to_string(), + stderr: "Failed to create pipes".to_string(), + exit_code: -1, + }; + } + }; // Redirect stdout and stderr to their respective pipes - if unsafe { dup2(pipe_stdout_fds[1], STDOUT_FILENO) } == -1 - || unsafe { dup2(pipe_stderr_fds[1], STDERR_FILENO) } == -1 - { - unsafe { - close(pipe_stdout_fds[0]); - close(pipe_stdout_fds[1]); - close(pipe_stderr_fds[0]); - close(pipe_stderr_fds[1]); - } + if dup2_stdout(&write_pipe_stdout).is_err() || dup2_stderr(&write_pipe_stderr).is_err() { return CommandResult { stdout: "".to_string(), stderr: "Failed to redirect STDOUT_FILENO or STDERR_FILENO".to_string(), @@ -110,29 +124,42 @@ where }; } - let original_stdin_fd = if let Some(input_str) = pipe_input { + // Handle stdin redirection if needed + let original_stdin_fd_owned = if let Some(input_str) = pipe_input { // we have pipe input let mut input_file = tempfile::tempfile().unwrap(); write!(input_file, "{input_str}").unwrap(); input_file.seek(SeekFrom::Start(0)).unwrap(); // Redirect stdin to read from the in-memory file - let original_stdin_fd = unsafe { dup(STDIN_FILENO) }; - if original_stdin_fd == -1 || unsafe { dup2(input_file.as_raw_fd(), STDIN_FILENO) } == -1 { + let stdin_fd = match dup(std::io::stdin()) { + Ok(fd) => fd, + Err(_) => { + return CommandResult { + stdout: "".to_string(), + stderr: "Failed to duplicate STDIN".to_string(), + exit_code: -1, + }; + } + }; + + // Redirect stdin to read from the in-memory file + if dup2_stdin(&input_file).is_err() { return CommandResult { stdout: "".to_string(), stderr: "Failed to set up stdin redirection".to_string(), exit_code: -1, }; } - Some(original_stdin_fd) + + Some(stdin_fd) } else { None }; let (uumain_exit_status, captured_stdout, captured_stderr) = thread::scope(|s| { - let out = s.spawn(|| read_from_fd(pipe_stdout_fds[0])); - let err = s.spawn(|| read_from_fd(pipe_stderr_fds[0])); + let out = s.spawn(|| read_from_fd(read_pipe_stdout.as_raw_fd())); + let err = s.spawn(|| read_from_fd(read_pipe_stderr.as_raw_fd())); #[allow(clippy::unnecessary_to_owned)] // TODO: clippy wants us to use args.iter().cloned() ? let status = uumain_function(args.to_owned().into_iter()); @@ -141,41 +168,25 @@ where uucore::error::set_exit_code(0); io::stdout().flush().unwrap(); io::stderr().flush().unwrap(); - unsafe { - close(pipe_stdout_fds[1]); - close(pipe_stderr_fds[1]); - close(STDOUT_FILENO); - close(STDERR_FILENO); - } + // Drop write ends to close them, allowing readers to get EOF + drop(write_pipe_stdout); + drop(write_pipe_stderr); + // Restore stdout/stderr + let _ = dup2_stdout(&original_stdout_fd_owned); + let _ = dup2_stderr(&original_stderr_fd_owned); (status, out.join().unwrap(), err.join().unwrap()) }); - // Restore the original stdout and stderr - if unsafe { dup2(original_stdout_fd, STDOUT_FILENO) } == -1 - || unsafe { dup2(original_stderr_fd, STDERR_FILENO) } == -1 + // Restore the original stdin if it was modified + if let Some(fd) = original_stdin_fd_owned + && dup2_stdin(&fd).is_err() { return CommandResult { stdout: "".to_string(), - stderr: "Failed to restore the original STDOUT_FILENO or STDERR_FILENO".to_string(), + stderr: "Failed to restore the original STDIN".to_string(), exit_code: -1, }; } - unsafe { - close(original_stdout_fd); - close(original_stderr_fd); - } - - // Restore the original stdin if it was modified - if let Some(fd) = original_stdin_fd { - if unsafe { dup2(fd, STDIN_FILENO) } == -1 { - return CommandResult { - stdout: "".to_string(), - stderr: "Failed to restore the original STDIN".to_string(), - exit_code: -1, - }; - } - unsafe { close(fd) }; - } CommandResult { stdout: captured_stdout, @@ -192,21 +203,27 @@ where fn read_from_fd(fd: RawFd) -> String { let mut captured_output = Vec::new(); let mut read_buffer = [0; 1024]; - loop { - let bytes_read = - unsafe { libc::read(fd, read_buffer.as_mut_ptr().cast(), read_buffer.len()) }; - if bytes_read == -1 { - eprintln!("Failed to read from the pipe"); - break; - } - if bytes_read == 0 { - break; + // Temporarily create an OwnedFd for reading (we won't drop it) + let owned_fd = unsafe { OwnedFd::from_raw_fd(fd) }; + + loop { + match read(&owned_fd, &mut read_buffer) { + Ok(bytes_read) => { + if bytes_read == 0 { + break; + } + captured_output.extend_from_slice(&read_buffer[..bytes_read]); + } + Err(_) => { + eprintln!("Failed to read from the pipe"); + break; + } } - captured_output.extend_from_slice(&read_buffer[..bytes_read as usize]); } - unsafe { libc::close(fd) }; + // Forget the owned_fd to prevent it from closing the fd (the caller owns it) + std::mem::forget(owned_fd); String::from_utf8_lossy(&captured_output).into_owned() }