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
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,7 @@ nix = { workspace = true, features = [
"user",
] }
rlimit = { workspace = true }
rustix.workspace = true


[build-dependencies]
Expand Down
3 changes: 3 additions & 0 deletions src/uu/tee/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ clap = { workspace = true }
uucore = { workspace = true, features = ["libc", "parser", "signals"] }
fluent = { workspace = true }

[target.'cfg(unix)'.dev-dependencies]
rustix = { workspace = true }

[[bin]]
name = "tee"
path = "src/main.rs"
103 changes: 34 additions & 69 deletions tests/by-util/test_tee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,38 +263,19 @@ mod linux_only {
use uutests::new_ucmd;

fn make_broken_pipe() -> File {
use libc::c_int;
use std::os::unix::io::FromRawFd;

let mut fds: [c_int; 2] = [0, 0];
assert_eq!(
unsafe { libc::pipe(std::ptr::from_mut::<c_int>(&mut fds[0])) },
0,
"Failed to create pipe"
);

let (read, write) = rustix::pipe::pipe().expect("Failed to create pipe");
// Drop the read end of the pipe
let _ = unsafe { File::from_raw_fd(fds[0]) };

drop(read);
// Make the write end of the pipe into a Rust File
Comment thread
oech3 marked this conversation as resolved.
unsafe { File::from_raw_fd(fds[1]) }
write.into()
}

fn make_hanging_read() -> File {
use libc::c_int;
use std::os::unix::io::FromRawFd;

let mut fds: [c_int; 2] = [0, 0];
assert_eq!(
unsafe { libc::pipe(std::ptr::from_mut::<c_int>(&mut fds[0])) },
0,
"Failed to create pipe"
);

let (read, write) = rustix::pipe::pipe().expect("Failed to create pipe");
// PURPOSELY leak the write end of the pipe, so the read end hangs.

std::mem::forget(write);
// Return the read end of the pipe
unsafe { File::from_raw_fd(fds[0]) }
read.into()
}

fn run_tee(proc: &mut UCommand) -> (String, CmdResult) {
Expand Down Expand Up @@ -745,56 +726,40 @@ fn test_output_error_flag_without_value_defaults_warn_nopipe() {
#[cfg(all(unix, not(target_os = "freebsd")))]
#[test]
fn test_output_error_presence_only_broken_pipe_unix() {
use std::fs::File;
use std::os::unix::io::FromRawFd;

unsafe {
let mut fds: [libc::c_int; 2] = [0, 0];
assert_eq!(libc::pipe(fds.as_mut_ptr()), 0, "Failed to create pipe");
// Close the read end to simulate a broken pipe on stdout
let _read_end = File::from_raw_fd(fds[0]);
let write_end = File::from_raw_fd(fds[1]);

let content = (0..10_000).map(|_| "x").collect::<String>();
let result = new_ucmd!()
.arg("--output-error") // presence-only flag
.set_stdout(write_end)
.pipe_in(content.as_bytes())
.run();

// Assert that a status was produced (i.e., process exited) and no crash occurred.
assert!(result.try_exit_status().is_some(), "process did not exit");
}
let (read, write) = rustix::pipe::pipe().expect("Failed to create pipe");
// Close the read end to simulate a broken pipe on stdout
drop(read);
let write: std::fs::File = write.into();
let content = (0..10_000).map(|_| "x").collect::<String>();
let result = new_ucmd!()
.arg("--output-error") // presence-only flag
.set_stdout(write)
.pipe_in(content.as_bytes())
.run();

// Assert that a status was produced (i.e., process exited) and no crash occurred.
assert!(result.try_exit_status().is_some(), "process did not exit");
}

// Skip on FreeBSD due to repeated CI hangs in FreeBSD VM (see PR #8684)
#[cfg(all(unix, not(target_os = "freebsd")))]
#[test]
fn test_broken_pipe_early_termination_stdout_only() {
use std::fs::File;
use std::os::unix::io::FromRawFd;

// Create a broken stdout by creating a pipe and dropping the read end
unsafe {
let mut fds: [libc::c_int; 2] = [0, 0];
assert_eq!(libc::pipe(fds.as_mut_ptr()), 0, "Failed to create pipe");
// Close the read end immediately to simulate a broken pipe
let _read_end = File::from_raw_fd(fds[0]);
let write_end = File::from_raw_fd(fds[1]);

let content = (0..10_000).map(|_| "x").collect::<String>();
let mut proc = new_ucmd!();
let result = proc
.set_stdout(write_end)
.ignore_stdin_write_error()
.pipe_in(content.as_bytes())
.run();

// GNU tee exits nonzero on broken pipe unless configured otherwise; implementation
// details vary by mode, but we should not panic and should return an exit status.
// Assert that a status was produced (i.e., process exited) and no crash occurred.
assert!(result.try_exit_status().is_some(), "process did not exit");
}
let (read, write) = rustix::pipe::pipe().expect("Failed to create pipe");
// Create a broken stdout
drop(read);
let write: std::fs::File = write.into();
let content = (0..10_000).map(|_| "x").collect::<String>();
let mut proc = new_ucmd!();
let result = proc
.set_stdout(write)
.ignore_stdin_write_error()
.pipe_in(content.as_bytes())
.run();
// GNU tee exits nonzero on broken pipe unless configured otherwise; implementation
// details vary by mode, but we should not panic and should return an exit status.
// Assert that a status was produced (i.e., process exited) and no crash occurred.
assert!(result.try_exit_status().is_some(), "process did not exit");
}

#[test]
Expand Down
Loading