stty: print special terminal information

This commit is contained in:
Terts Diepraam 2022-07-01 15:55:58 +02:00 committed by Sylvestre Ledru
parent cc147a7c8d
commit f861fc0854
3 changed files with 214 additions and 18 deletions

View file

@ -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<C>; 12] = [
Flag::new("parenb", C::PARENB),
@ -19,7 +21,7 @@ pub const CONTROL_FLAGS: [Flag<C>; 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<L>; 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),
];

View file

@ -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<T: TermiosFlag>(termios: &Termios, opts: &Options, flags: &[Flag<T>]) {
@ -169,14 +228,14 @@ fn print_flags<T: TermiosFlag>(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)

View file

@ -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",
);
}