diff --git a/src/uu/stty/src/stty.rs b/src/uu/stty/src/stty.rs index d6e46d90916..8bbe71eac7a 100644 --- a/src/uu/stty/src/stty.rs +++ b/src/uu/stty/src/stty.rs @@ -980,22 +980,23 @@ fn print_flags( fn apply_setting(termios: &mut Termios, setting: &AllFlags) -> nix::Result<()> { match setting { AllFlags::Baud(_, _) => apply_baud_rate_flag(termios, setting)?, - AllFlags::ControlFlags((setting, disable)) => { - setting.flag.apply(termios, !disable); - } - AllFlags::InputFlags((setting, disable)) => { - setting.flag.apply(termios, !disable); - } - AllFlags::LocalFlags((setting, disable)) => { - setting.flag.apply(termios, !disable); - } - AllFlags::OutputFlags((setting, disable)) => { - setting.flag.apply(termios, !disable); - } + AllFlags::ControlFlags((setting, disable)) => apply_flag(termios, setting, !disable), + AllFlags::InputFlags((setting, disable)) => apply_flag(termios, setting, !disable), + AllFlags::LocalFlags((setting, disable)) => apply_flag(termios, setting, !disable), + AllFlags::OutputFlags((setting, disable)) => apply_flag(termios, setting, !disable), } Ok(()) } +fn apply_flag(termios: &mut Termios, setting: &Flag, val: bool) { + // A grouped flag selects one value of a multi-bit field (e.g. `cs7` in CSIZE), + // so the bits of the previously selected value have to be cleared first. + if val && let Some(group) = setting.group { + group.apply(termios, false); + } + setting.flag.apply(termios, val); +} + fn apply_baud_rate_flag(termios: &mut Termios, input: &AllFlags) -> nix::Result<()> { if let AllFlags::Baud(rate, baud_type) = input { match baud_type { @@ -1586,6 +1587,40 @@ mod tests { assert!(!check_flag_group(&flag_without_group, false)); } + // The character size is tested here rather than on a pseudo-terminal, + // because Linux pseudo-terminals always keep cs8. + #[test] + fn test_apply_grouped_flag_replaces_previous_value() { + // SAFETY: all-zero bytes are a valid `termios`. + let mut termios = Termios::from(unsafe { std::mem::zeroed::() }); + let apply = |termios: &mut Termios, arg: &str| { + let flags = string_to_flag(arg) + .map_or_else(|| combo_to_flags(arg), |flag| vec![ArgOptions::Flags(flag)]); + for flag in flags { + if let ArgOptions::Flags(flag) = flag { + apply_setting(termios, &flag).unwrap(); + } + } + }; + + for (arg, expected) in [ + ("cs8", ControlFlags::CS8), + ("cs7", ControlFlags::CS7), + ("cs5", ControlFlags::CS5), + ("cs6", ControlFlags::CS6), + ("cs8", ControlFlags::CS8), + ("evenp", ControlFlags::CS7), + ("-evenp", ControlFlags::CS8), + ] { + apply(&mut termios, arg); + assert_eq!( + termios.control_flags & ControlFlags::CSIZE, + expected, + "after {arg}" + ); + } + } + // Additional tests for get_sane_control_char #[test] fn test_get_sane_control_char_all_defined() { diff --git a/tests/by-util/test_stty.rs b/tests/by-util/test_stty.rs index 157758db903..1423c8f6a2f 100644 --- a/tests/by-util/test_stty.rs +++ b/tests/by-util/test_stty.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore parenb parmrk ixany iuclc onlcr ofdel icanon noflsh econl igpar ispeed ospeed NCCS nonhex gstty notachar cbreak evenp oddp CSIZE +// spell-checker:ignore parenb parmrk ixany iuclc onlcr ofdel icanon noflsh econl igpar ispeed ospeed NCCS nonhex gstty notachar cbreak evenp oddp CSIZE tabdly #![cfg(unix)] @@ -64,6 +64,53 @@ fn test_sane() { .stdout_str_check(|s| !s.contains("intr = ^A")); } +/// Returns the settings from `group` that `stty --all` reports for the terminal at `path`. +#[cfg(any( + target_vendor = "apple", + target_os = "android", + target_os = "haiku", + target_os = "linux" +))] +fn shown_settings(path: &str, group: &[&'static str]) -> Vec<&'static str> { + let stdout = new_ucmd!() + .args(&["--all", "--file", path]) + .succeeds() + .stdout_move_str(); + let words: Vec<&str> = stdout.split_whitespace().collect(); + group + .iter() + .copied() + .filter(|s| words.contains(s)) + .collect() +} + +#[test] +#[cfg(any( + target_vendor = "apple", + target_os = "android", + target_os = "haiku", + target_os = "linux" +))] +fn test_grouped_output_flag_replaces_previous_value() { + let (path, _controller, _replica) = pty_path(); + let tabdly = ["tab0", "tab1", "tab2", "tab3"]; + + for (setting, expected) in [ + ("tab1", "tab1"), + ("tab2", "tab2"), + ("tab0", "tab0"), + ("tab3", "tab3"), + ("sane", "tab0"), + ] { + new_ucmd!().args(&["--file", &path, setting]).succeeds(); + assert_eq!( + shown_settings(&path, &tabdly), + [expected], + "after {setting}" + ); + } +} + #[test] fn save_and_setting() { new_ucmd!()