Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion fuzz/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion fuzz/uufuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
173 changes: 95 additions & 78 deletions fuzz/uufuzz/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -67,72 +70,96 @@ pub fn generate_and_run_uumain<F>(
where
F: FnOnce(std::vec::IntoIter<OsString>) -> 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(),
exit_code: -1,
};
}

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());
Expand All @@ -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,
Expand All @@ -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()
}
Expand Down
Loading