diff --git a/src/uu/tty/src/tty.rs b/src/uu/tty/src/tty.rs index 1469948b888..1e82d2cd0f6 100644 --- a/src/uu/tty/src/tty.rs +++ b/src/uu/tty/src/tty.rs @@ -33,20 +33,31 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } let mut stdout = std::io::stdout(); - let name = nix::unistd::ttyname(std::io::stdin()); let write_result = match name { - Ok(name) => stdout.write_all_os(name.as_os_str()), + Ok(name) => { + if let Err(e) = stdout.write_all_os(name.as_os_str()) { + Err(e) + } else if let Err(e) = writeln!(stdout) { + Err(e) + } else { + stdout.flush() + } + } Err(_) => { set_exit_code(1); writeln!(stdout, "{}", translate!("tty-not-a-tty")) } }; - if write_result.is_err() || stdout.flush().is_err() { - // Don't return to prevent a panic later when another flush is attempted - // because the `uucore_procs::main` macro inserts a flush after execution for every utility. + if let Err(e) = write_result { + eprintln!("tty: write error: {}", e); + std::process::exit(3); + } + + if let Err(e) = stdout.flush() { + eprintln!("tty: write error: {}", e); std::process::exit(3); } diff --git a/tests/by-util/test_tty.rs b/tests/by-util/test_tty.rs index 9e1db7a21c2..db16e23b792 100644 --- a/tests/by-util/test_tty.rs +++ b/tests/by-util/test_tty.rs @@ -87,3 +87,44 @@ fn test_stdout_fail() { let status = proc.wait().unwrap(); assert_eq!(status.code(), Some(3)); } + +#[test] +#[cfg(all(unix, not(target_os = "freebsd")))] +fn test_newline_in_output() { + // Test that output ends with a newline, regardless of success or failure + let result = new_ucmd!().run(); + let stdout = result.stdout_str(); + + // Whether it's "not a tty\n" or an actual tty path, it should end with newline + assert!( + stdout.ends_with('\n'), + "Output should end with newline, got: {:?}", + stdout + ); +} + +#[test] +#[cfg(all(unix, not(target_os = "freebsd")))] +#[ignore = "Fails on some CI environments - see https://github.com/uutils/coreutils/pull/10252"] +fn test_write_error_dev_full() { + use std::process::{Command, Stdio}; + use uutests::at_and_ts; + + let (_, ts) = at_and_ts!(); + + // Redirect stdout to /dev/full which causes write errors + let output = Command::new(ts.bin_path.to_str().unwrap()) + .arg(&ts.util_name) + .stdout(Stdio::from(File::create("/dev/full").unwrap())) + .output() + .unwrap(); + + // Should exit with code 3 and print error to stderr + assert_eq!(output.status.code(), Some(3)); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains("write error"), + "Should contain 'write error' in stderr, got: {:?}", + stderr + ); +}