diff --git a/src/uu/stty/src/stty.rs b/src/uu/stty/src/stty.rs index 313b3de6c8..ccacddfe20 100644 --- a/src/uu/stty/src/stty.rs +++ b/src/uu/stty/src/stty.rs @@ -520,6 +520,16 @@ fn apply_flag( } fn apply_baud_rate_flag(termios: &mut Termios, input: &str) -> ControlFlow> { + fn map_cfsetospeed_result(result: nix::Result<()>) -> UResult { + match result { + Ok(()) => Ok(true), + Err(er) => Err(USimpleError::new( + 1, + format!("failed to set baud rate: errno {er}"), + )), + } + } + // BSDs use a u32 for the baud rate, so any decimal number applies. #[cfg(any( target_os = "freebsd", @@ -530,14 +540,9 @@ fn apply_baud_rate_flag(termios: &mut Termios, input: &str) -> ControlFlow() { - if let Err(er) = cfsetospeed(termios, n) { - return Err(USimpleError::new( - 1, - format!("failed to set baud rate: errno {er}"), - )); - } + let result = map_cfsetospeed_result(cfsetospeed(termios, n)); - return ControlFlow::Break(Ok(true)); + return ControlFlow::Break(result); } // Other platforms use an enum. @@ -551,14 +556,9 @@ fn apply_baud_rate_flag(termios: &mut Termios, input: &str) -> ControlFlow &'static Regex { + static ONCE_CELL: OnceCell = OnceCell::::new(); + + ONCE_CELL.get_or_init(|| { + // e.g.: + // speed 38400 baud; line = 0; + Regex::new("speed [0-9]+ baud; line = [0-9]+;").unwrap() + }) +} + +fn get_print_dash_a_first_line_regex() -> &'static Regex { + static ONCE_CELL: OnceCell = OnceCell::::new(); + + ONCE_CELL.get_or_init(|| { + // e.g.: + // speed 38400 baud; rows 54; columns 216; line = 0; + Regex::new("speed [0-9]+ baud; rows [0-9]+; columns [0-9]+; line = [0-9]+;").unwrap() + }) +} + +fn get_dev_tty_stdio() -> Stdio { + use std::os::fd::FromRawFd; + + let dev_tty_raw_fd = fcntl::open(DEV_TTY, OFlag::O_NONBLOCK, Mode::empty()).unwrap(); + + // TODO + // Verify safety + unsafe { Stdio::from_raw_fd(dev_tty_raw_fd) } +} + +impl UCommand { + fn set_stdin_to_dev_tty_stdio(&mut self) -> &mut Self { + self.set_stdin(get_dev_tty_stdio()) + } +} #[test] +#[cfg(not(target_os = "android"))] fn test_invalid_arg() { new_ucmd!() - .args(&["--file=/dev/tty", "--definitely-invalid"]) + .arg("--definitely-invalid") + .set_stdin_to_dev_tty_stdio() .fails() .code_is(1); } #[test] +#[cfg(not(target_os = "android"))] fn runs() { - new_ucmd!().arg("--file=/dev/tty").succeeds(); + new_ucmd!().set_stdin_to_dev_tty_stdio().succeeds(); } #[test] +#[cfg(not(target_os = "android"))] fn print_all() { - let cmd_result = new_ucmd!().args(&["--file=/dev/tty", "-a"]).succeeds(); + let cmd_result = new_ucmd!() + .arg("-a") + .set_stdin_to_dev_tty_stdio() + .succeeds(); // "iuclc" removed due to this comment in stty.rs: // @@ -73,9 +124,11 @@ fn save_and_all() { // Make sure the "allow_hyphen_values" clap function has been called with true #[test] +#[cfg(not(target_os = "android"))] fn negation() { new_ucmd!() - .args(&["--file=/dev/tty", "-ixon"]) + .arg("-ixon") + .set_stdin_to_dev_tty_stdio() .succeeds() .stdout_is_bytes([]) .stderr_is_bytes([]); @@ -84,6 +137,7 @@ fn negation() { fn succeeds_test_with_regex(args: &[&str], stdout_regex: &Regex) { new_ucmd!() .args(args) + .set_stdin_to_dev_tty_stdio() .succeeds() .stdout_str_check(|st| { let Some(str) = st.lines().next() else { @@ -97,32 +151,87 @@ fn succeeds_test_with_regex(args: &[&str], stdout_regex: &Regex) { // The end of options delimiter ("--") and everything after must be ignored #[test] +#[cfg(not(target_os = "android"))] fn ignore_end_of_options_and_after() { { - // e.g.: - // speed 38400 baud; rows 54; columns 216; line = 0; - let regex = - Regex::new("speed [0-9]+ baud; rows [0-9]+; columns [0-9]+; line = [0-9]+;").unwrap(); - // "stty -a -- -ixon" should behave like "stty -a" // Should not abort with an error complaining about passing both "-a" and "-ixon" (since "-ixon" is after "--") - succeeds_test_with_regex(&["--file=/dev/tty", "-a", "--", "-ixon"], ®ex); + succeeds_test_with_regex(&["-a", "--", "-ixon"], get_print_dash_a_first_line_regex()); } { - // e.g.: - // speed 38400 baud; line = 0; - let regex = Regex::new("speed [0-9]+ baud; line = [0-9]+;").unwrap(); - // "stty -- non-existent-option-that-must-be-ignore" should behave like "stty" // Should not abort with an error complaining about an invalid argument, since the invalid argument is after "--" succeeds_test_with_regex( - &[ - "--file=/dev/tty", - "--", - "non-existent-option-that-must-be-ignored", - ], - ®ex, + &["--", "non-existent-option-that-must-be-ignored"], + get_print_first_line_regex(), ); } } + +#[test] +fn f_file_option() { + for st in ["-F", "--file"] { + for bo in [false, true] { + let (args, regex): (&[&str], &'static Regex) = if bo { + (&[st, DEV_TTY, "-a"], get_print_dash_a_first_line_regex()) + } else { + (&[st, DEV_TTY], get_print_first_line_regex()) + }; + + new_ucmd!() + .args(args) + .set_stdin_to_dev_tty_stdio() + .succeeds() + .stdout_str_check(|st| { + let Some(str) = st.lines().next() else { + return false; + }; + + regex.is_match(str) + }) + .no_stderr(); + } + } +} + +// Make sure stty is using stdin to look up terminal attributes, not stdout +#[test] +fn correct_file_descriptor_output_piped() { + const PIPE_STDOUT_TO: &str = "pipe_stdout_to"; + const PIPE_STDERR_TO: &str = "pipe_stderr_to"; + + let test_scenario = TestScenario::new(util_name!()); + + let at_path = &test_scenario.fixtures; + + let stdout_file = at_path.make_file(PIPE_STDOUT_TO); + let stderr_file = at_path.make_file(PIPE_STDERR_TO); + + test_scenario + .ucmd() + .set_stdin_to_dev_tty_stdio() + .set_stdout(Stdio::from(stdout_file)) + .set_stderr(Stdio::from(stderr_file)) + .succeeds(); + + let mut read_to_string_buffer = String::new(); + + at_path + .open(PIPE_STDOUT_TO) + .read_to_string(&mut read_to_string_buffer) + .unwrap(); + + let stdout_first_line = read_to_string_buffer.lines().next().unwrap(); + + assert!(get_print_first_line_regex().is_match(stdout_first_line)); + + read_to_string_buffer.clear(); + + at_path + .open(PIPE_STDERR_TO) + .read_to_string(&mut read_to_string_buffer) + .unwrap(); + + assert!(read_to_string_buffer.is_empty()); +}