diff --git a/src/uu/stty/src/stty.rs b/src/uu/stty/src/stty.rs index f4d71ae857a..517a3472f16 100644 --- a/src/uu/stty/src/stty.rs +++ b/src/uu/stty/src/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 clocal erange tcgetattr tcsetattr tcsanow tiocgwinsz tiocswinsz cfgetospeed cfsetospeed ushort vmin vtime cflag lflag ispeed ospeed +// spell-checker:ignore clocal erange tcgetattr tcsetattr tcsanow tiocgwinsz tiocswinsz cfgetospeed cfsetospeed ushort vmin vtime cflag lflag ispeed ospeed openpty // spell-checker:ignore parenb parodd cmspar hupcl cstopb cread clocal crtscts CSIZE // spell-checker:ignore ignbrk brkint ignpar parmrk inpck istrip inlcr igncr icrnl ixoff ixon iuclc ixany imaxbel iutf // spell-checker:ignore opost olcuc ocrnl onlcr onocr onlret ofdel nldly crdly tabdly bsdly vtdly ffdly ofill @@ -101,6 +101,16 @@ impl Flag { self.sane = true; self } + + fn apply(&self, termios: &mut Termios, enabled: bool) + where + T: TermiosFlag, + { + if enabled && let Some(group) = self.group { + group.apply(termios, false); + } + self.flag.apply(termios, enabled); + } } trait TermiosFlag: Copy { @@ -892,16 +902,16 @@ 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); + setting.apply(termios, !disable); } AllFlags::InputFlags((setting, disable)) => { - setting.flag.apply(termios, !disable); + setting.apply(termios, !disable); } AllFlags::LocalFlags((setting, disable)) => { - setting.flag.apply(termios, !disable); + setting.apply(termios, !disable); } AllFlags::OutputFlags((setting, disable)) => { - setting.flag.apply(termios, !disable); + setting.apply(termios, !disable); } } Ok(()) @@ -1216,6 +1226,29 @@ impl TermiosFlag for LocalFlags { mod tests { use super::*; + #[test] + #[cfg(not(any(target_os = "aix", target_os = "redox", target_os = "fuchsia")))] + fn test_apply_character_size_preserves_other_control_flags() { + let pty = nix::pty::openpty(None, None).unwrap(); + let mut termios = tcgetattr(&pty.slave).unwrap(); + let other_flags = ControlFlags::CLOCAL | ControlFlags::CREAD; + let sizes = [ + ("cs5", ControlFlags::CS5), + ("cs6", ControlFlags::CS6), + ("cs7", ControlFlags::CS7), + ("cs8", ControlFlags::CS8), + ]; + + for (_, initial) in sizes { + for (name, expected) in sizes { + termios.control_flags = initial | other_flags; + let flag = CONTROL_FLAGS.iter().find(|flag| flag.name == name).unwrap(); + apply_setting(&mut termios, &AllFlags::ControlFlags((flag, false))).unwrap(); + assert_eq!(termios.control_flags, expected | other_flags); + } + } + } + // Essential unit tests for complex internal parsing and logic functions. // Control character parsing tests diff --git a/tests/by-util/test_stty.rs b/tests/by-util/test_stty.rs index 157758db903..ba23b5bd725 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 tcgetattr CRDLY #![cfg(unix)] @@ -46,6 +46,73 @@ fn test_all_flag() { } } +#[cfg(target_os = "linux")] +#[test] +fn test_character_size_cs8_then_cs7() { + use rustix::termios::{ControlModes, tcgetattr}; + + let (path, _controller, replica) = pty_path(); + new_ucmd!() + .args(&["--file", &path, "cs8"]) + .succeeds() + .no_output(); + + // Applying a saved state bypasses grouped flag handling while using the same + // libc interface, whose treatment of unsupported PTY settings can vary. + let original = new_ucmd!() + .args(&["--file", &path, "--save"]) + .succeeds() + .stdout_move_str(); + let mut expected: Vec<_> = original.trim().split(':').map(str::to_owned).collect(); + let control_modes = tcgetattr(&replica).unwrap().control_modes; + expected[2] = format!( + "{:x}", + ((control_modes - ControlModes::CSIZE) | ControlModes::CS7).bits() + ); + let expected_result = new_ucmd!() + .args(&["--file", &path, &expected.join(":")]) + .run(); + let expected_size = tcgetattr(&replica).unwrap().control_modes & ControlModes::CSIZE; + new_ucmd!() + .args(&["--file", &path, original.trim()]) + .succeeds() + .no_output(); + + let result = new_ucmd!().args(&["--file", &path, "cs7"]).run(); + result + .code_is(expected_result.code()) + .no_stdout() + .stderr_is(expected_result.stderr_str()); + assert_eq!( + tcgetattr(&replica).unwrap().control_modes & ControlModes::CSIZE, + expected_size + ); +} + +#[cfg(target_os = "linux")] +#[rstest::rstest] +#[case("cr3", "cr0", rustix::termios::OutputModes::CR0)] +#[case("cr3", "cr1", rustix::termios::OutputModes::CR1)] +#[case("cr1", "cr2", rustix::termios::OutputModes::CR2)] +#[case("cr2", "cr3", rustix::termios::OutputModes::CR3)] +fn test_grouped_output_flags( + #[case] initial: &str, + #[case] setting: &str, + #[case] expected: rustix::termios::OutputModes, +) { + use rustix::termios::{OutputModes, tcgetattr}; + + let (path, _controller, replica) = pty_path(); + new_ucmd!() + .args(&["--file", &path, "opost", initial, setting]) + .succeeds() + .no_output(); + + let termios = tcgetattr(&replica).unwrap(); + assert_eq!(termios.output_modes & OutputModes::CRDLY, expected); + assert!(termios.output_modes.contains(OutputModes::OPOST)); +} + #[test] fn test_sane() { let (path, _controller, _replica) = pty_path();