diff --git a/src/uu/stty/src/flags.rs b/src/uu/stty/src/flags.rs index 226c5ce668b..14042ff7045 100644 --- a/src/uu/stty/src/flags.rs +++ b/src/uu/stty/src/flags.rs @@ -9,7 +9,9 @@ // spell-checker:ignore isig icanon iexten echoe crterase echok echonl noflsh xcase tostop echoprt prterase echoctl ctlecho echoke crtkill flusho extproc use crate::Flag; -use nix::sys::termios::{ControlFlags as C, InputFlags as I, LocalFlags as L, OutputFlags as O}; +use nix::sys::termios::{ + BaudRate, ControlFlags as C, InputFlags as I, LocalFlags as L, OutputFlags as O, +}; pub const CONTROL_FLAGS: [Flag; 12] = [ Flag::new("parenb", C::PARENB), @@ -19,7 +21,7 @@ pub const CONTROL_FLAGS: [Flag; 12] = [ Flag::new("cs6", C::CS6).group(C::CSIZE), Flag::new("cs7", C::CS7).group(C::CSIZE), Flag::new("cs8", C::CS8).group(C::CSIZE).sane(), - Flag::new("hupcl", C::HUPCL).sane(), + Flag::new("hupcl", C::HUPCL), Flag::new("cstopb", C::CSTOPB), Flag::new("cread", C::CREAD).sane(), Flag::new("clocal", C::CLOCAL), @@ -95,3 +97,103 @@ pub const LOCAL_FLAGS: [Flag; 18] = [ Flag::new("flusho", L::FLUSHO), Flag::new("extproc", L::EXTPROC), ]; + +pub const BAUD_RATES: &[(&str, BaudRate)] = &[ + ("0", BaudRate::B0), + ("50", BaudRate::B50), + ("75", BaudRate::B75), + ("110", BaudRate::B110), + ("134", BaudRate::B134), + ("150", BaudRate::B150), + ("200", BaudRate::B200), + ("300", BaudRate::B300), + ("600", BaudRate::B600), + ("1200", BaudRate::B1200), + ("1800", BaudRate::B1800), + ("2400", BaudRate::B2400), + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + ("4800", BaudRate::B4800), + ("9600", BaudRate::B9600), + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + ("14400", BaudRate::B14400), + ("19200", BaudRate::B19200), + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + ("28800", BaudRate::B28800), + ("38400", BaudRate::B38400), + ("57600", BaudRate::B57600), + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + ("76800", BaudRate::B76800), + ("115200", BaudRate::B115200), + ("230400", BaudRate::B230400), + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + ("460800", BaudRate::B460800), + #[cfg(any(target_os = "android", target_os = "linux"))] + ("500000", BaudRate::B500000), + #[cfg(any(target_os = "android", target_os = "linux"))] + ("576000", BaudRate::B576000), + #[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd" + ))] + ("921600", BaudRate::B921600), + #[cfg(any(target_os = "android", target_os = "linux"))] + ("1000000", BaudRate::B1000000), + #[cfg(any(target_os = "android", target_os = "linux"))] + ("1152000", BaudRate::B1152000), + #[cfg(any(target_os = "android", target_os = "linux"))] + ("1500000", BaudRate::B1500000), + #[cfg(any(target_os = "android", target_os = "linux"))] + ("2000000", BaudRate::B2000000), + #[cfg(any( + target_os = "android", + all(target_os = "linux", not(target_arch = "sparc64")) + ))] + ("2500000", BaudRate::B2500000), + #[cfg(any( + target_os = "android", + all(target_os = "linux", not(target_arch = "sparc64")) + ))] + ("3000000", BaudRate::B3000000), + #[cfg(any( + target_os = "android", + all(target_os = "linux", not(target_arch = "sparc64")) + ))] + ("3500000", BaudRate::B3500000), + #[cfg(any( + target_os = "android", + all(target_os = "linux", not(target_arch = "sparc64")) + ))] + ("4000000", BaudRate::B4000000), +]; diff --git a/src/uu/stty/src/stty.rs b/src/uu/stty/src/stty.rs index 68c128a7b19..5cabe6da60d 100644 --- a/src/uu/stty/src/stty.rs +++ b/src/uu/stty/src/stty.rs @@ -3,21 +3,23 @@ // * For the full copyright and license information, please view the LICENSE file // * that was distributed with this source code. -// spell-checker:ignore tcgetattr tcsetattr tcsanow +// spell-checker:ignore tcgetattr tcsetattr tcsanow tiocgwinsz tiocswinsz cfgetospeed ushort mod flags; use clap::{crate_version, Arg, ArgMatches, Command}; +use nix::libc::{c_ushort, TIOCGWINSZ, TIOCSWINSZ}; use nix::sys::termios::{ - tcgetattr, tcsetattr, ControlFlags, InputFlags, LocalFlags, OutputFlags, Termios, + cfgetospeed, tcgetattr, tcsetattr, ControlFlags, InputFlags, LocalFlags, OutputFlags, Termios, }; +use nix::{ioctl_read_bad, ioctl_write_ptr_bad}; use std::io::{self, stdout}; use std::ops::ControlFlow; use std::os::unix::io::{AsRawFd, RawFd}; use uucore::error::{UResult, USimpleError}; use uucore::{format_usage, InvalidEncodingHandling}; -use flags::{CONTROL_FLAGS, INPUT_FLAGS, LOCAL_FLAGS, OUTPUT_FLAGS}; +use flags::{BAUD_RATES, CONTROL_FLAGS, INPUT_FLAGS, LOCAL_FLAGS, OUTPUT_FLAGS}; const NAME: &str = "stty"; const USAGE: &str = "\ @@ -104,6 +106,30 @@ impl<'a> Options<'a> { } } +// Needs to be repr(C) because we pass it to the ioctl calls. +#[repr(C)] +#[derive(Default, Debug)] +pub struct TermSize { + rows: c_ushort, + columns: c_ushort, + x: c_ushort, + y: c_ushort, +} + +ioctl_read_bad!( + /// Get terminal window size + tiocgwinsz, + TIOCGWINSZ, + TermSize +); + +ioctl_write_ptr_bad!( + /// Set terminal window size + tiocswinsz, + TIOCSWINSZ, + TermSize +); + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args @@ -118,17 +144,24 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } fn stty(opts: &Options) -> UResult<()> { - // TODO: Figure out the right error message + if opts.save && opts.all { + return Err(USimpleError::new( + 1, + "the options for verbose and stty-readable output styles are mutually exclusive", + )); + } + + if opts.settings.is_some() && (opts.save || opts.all) { + return Err(USimpleError::new( + 1, + "when specifying an output style, modes may not be set", + )); + } + + // TODO: Figure out the right error message for when tcgetattr fails let mut termios = tcgetattr(opts.file).expect("Could not get terminal attributes"); if let Some(settings) = &opts.settings { - if opts.save || opts.all { - return Err(USimpleError::new( - 1, - "when specifying an output style, modes may not be set", - )); - } - for setting in settings { if let ControlFlow::Break(false) = apply_setting(&mut termios, setting) { return Err(USimpleError::new( @@ -141,16 +174,42 @@ fn stty(opts: &Options) -> UResult<()> { tcsetattr(opts.file, nix::sys::termios::SetArg::TCSANOW, &termios) .expect("Could not write terminal attributes"); } else { - print_settings(&termios, opts); + print_settings(&termios, opts).expect("TODO: make proper error here from nix error"); } Ok(()) } -fn print_settings(termios: &Termios, opts: &Options) { +fn print_terminal_size(termios: &Termios, opts: &Options) -> nix::Result<()> { + let speed = cfgetospeed(termios); + for (text, baud_rate) in BAUD_RATES { + if *baud_rate == speed { + print!("speed {} baud; ", text); + break; + } + } + + if opts.all { + let mut size = TermSize::default(); + unsafe { tiocgwinsz(opts.file, &mut size as *mut _)? }; + print!("rows {}; columns {}; ", size.rows, size.columns); + } + + // For some reason the normal nix Termios struct does not expose the line, + // so we get the underlying libc::termios struct to get that information. + let libc_termios: nix::libc::termios = termios.clone().into(); + let line = libc_termios.c_line; + print!("line = {};", line); + println!(); + Ok(()) +} + +fn print_settings(termios: &Termios, opts: &Options) -> nix::Result<()> { + print_terminal_size(termios, opts)?; print_flags(termios, opts, &CONTROL_FLAGS); print_flags(termios, opts, &INPUT_FLAGS); print_flags(termios, opts, &OUTPUT_FLAGS); print_flags(termios, opts, &LOCAL_FLAGS); + Ok(()) } fn print_flags(termios: &Termios, opts: &Options, flags: &[Flag]) { @@ -169,14 +228,14 @@ fn print_flags(termios: &Termios, opts: &Options, flags: &[Flag< let val = flag.is_in(termios, group); if group.is_some() { if val && (!sane || opts.all) { - print!("{name} "); + print!("{} ", name); printed = true; } } else if opts.all || val != sane { if !val { print!("-"); } - print!("{name} "); + print!("{} ", name); printed = true; } } @@ -258,7 +317,7 @@ pub fn uu_app<'a>() -> Command<'a> { .takes_value(true) .value_hint(clap::ValueHint::FilePath) .value_name("DEVICE") - .help("open and use the specified DEVICE instead of stdin") + .help("open and use the specified DEVICE instead of stdin"), ) .arg( Arg::new(options::SETTINGS) diff --git a/tests/by-util/test_stty.rs b/tests/by-util/test_stty.rs index 7dc0b20d2e8..730ccdf3723 100644 --- a/tests/by-util/test_stty.rs +++ b/tests/by-util/test_stty.rs @@ -3,11 +3,13 @@ use crate::common::util::*; #[test] +#[ignore = "Fails because cargo test does not run in a tty"] fn runs() { new_ucmd!().succeeds(); } #[test] +#[ignore = "Fails because cargo test does not run in a tty"] fn print_all() { let res = new_ucmd!().succeeds(); @@ -18,3 +20,36 @@ fn print_all() { res.stdout_contains(flag); } } + +#[test] +fn save_and_setting() { + new_ucmd!() + .args(&["--save", "nl0"]) + .fails() + .stderr_contains("when specifying an output style, modes may not be set"); +} + +#[test] +fn all_and_setting() { + new_ucmd!() + .args(&["--all", "nl0"]) + .fails() + .stderr_contains("when specifying an output style, modes may not be set"); +} + +#[test] +fn save_and_all() { + new_ucmd!() + .args(&["--save", "--all"]) + .fails() + .stderr_contains( + "the options for verbose and stty-readable output styles are mutually exclusive", + ); + + new_ucmd!() + .args(&["--all", "--save"]) + .fails() + .stderr_contains( + "the options for verbose and stty-readable output styles are mutually exclusive", + ); +}