mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-27 11:07:44 +00:00
Merge pull request #8309 from willshuttleworth/stty-special-settings
`stty`: special settings
This commit is contained in:
commit
2ee135e23e
2 changed files with 144 additions and 10 deletions
|
@ -4,6 +4,7 @@
|
||||||
// file that was distributed with this source code.
|
// 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
|
||||||
|
// spell-checker:ignore tcsadrain
|
||||||
|
|
||||||
mod flags;
|
mod flags;
|
||||||
|
|
||||||
|
@ -11,13 +12,14 @@ use crate::flags::AllFlags;
|
||||||
use clap::{Arg, ArgAction, ArgMatches, Command};
|
use clap::{Arg, ArgAction, ArgMatches, Command};
|
||||||
use nix::libc::{O_NONBLOCK, TIOCGWINSZ, TIOCSWINSZ, c_ushort};
|
use nix::libc::{O_NONBLOCK, TIOCGWINSZ, TIOCSWINSZ, c_ushort};
|
||||||
use nix::sys::termios::{
|
use nix::sys::termios::{
|
||||||
ControlFlags, InputFlags, LocalFlags, OutputFlags, SpecialCharacterIndices, Termios,
|
ControlFlags, InputFlags, LocalFlags, OutputFlags, SetArg, SpecialCharacterIndices, Termios,
|
||||||
cfgetospeed, cfsetospeed, tcgetattr, tcsetattr,
|
cfgetospeed, cfsetospeed, tcgetattr, tcsetattr,
|
||||||
};
|
};
|
||||||
use nix::{ioctl_read_bad, ioctl_write_ptr_bad};
|
use nix::{ioctl_read_bad, ioctl_write_ptr_bad};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, Stdout, stdout};
|
use std::io::{self, Stdout, stdout};
|
||||||
|
use std::num::IntErrorKind;
|
||||||
use std::os::fd::{AsFd, BorrowedFd};
|
use std::os::fd::{AsFd, BorrowedFd};
|
||||||
use std::os::unix::fs::OpenOptionsExt;
|
use std::os::unix::fs::OpenOptionsExt;
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
@ -112,6 +114,7 @@ enum ControlCharMappingError {
|
||||||
enum SpecialSetting {
|
enum SpecialSetting {
|
||||||
Rows(u16),
|
Rows(u16),
|
||||||
Cols(u16),
|
Cols(u16),
|
||||||
|
Line(u8),
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PrintSetting {
|
enum PrintSetting {
|
||||||
|
@ -238,6 +241,7 @@ fn stty(opts: &Options) -> UResult<()> {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut set_arg = SetArg::TCSADRAIN;
|
||||||
let mut valid_args: Vec<ArgOptions> = Vec::new();
|
let mut valid_args: Vec<ArgOptions> = Vec::new();
|
||||||
|
|
||||||
if let Some(args) = &opts.settings {
|
if let Some(args) = &opts.settings {
|
||||||
|
@ -299,7 +303,59 @@ fn stty(opts: &Options) -> UResult<()> {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// baud rate setting
|
} else if *arg == "line" {
|
||||||
|
match args_iter.next() {
|
||||||
|
Some(line) => match parse_u8_or_err(line) {
|
||||||
|
Ok(n) => valid_args.push(ArgOptions::Special(SpecialSetting::Line(n))),
|
||||||
|
Err(e) => return Err(USimpleError::new(1, e)),
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
return Err(USimpleError::new(
|
||||||
|
1,
|
||||||
|
get_message_with_args(
|
||||||
|
"stty-error-missing-argument",
|
||||||
|
HashMap::from([("arg".to_string(), arg.to_string())]),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if *arg == "min" {
|
||||||
|
match args_iter.next() {
|
||||||
|
Some(min) => match parse_u8_or_err(min) {
|
||||||
|
Ok(n) => {
|
||||||
|
valid_args
|
||||||
|
.push(ArgOptions::Mapping((SpecialCharacterIndices::VMIN, n)));
|
||||||
|
}
|
||||||
|
Err(e) => return Err(USimpleError::new(1, e)),
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
return Err(USimpleError::new(
|
||||||
|
1,
|
||||||
|
get_message_with_args(
|
||||||
|
"stty-error-missing-argument",
|
||||||
|
HashMap::from([("arg".to_string(), arg.to_string())]),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if *arg == "time" {
|
||||||
|
match args_iter.next() {
|
||||||
|
Some(time) => match parse_u8_or_err(time) {
|
||||||
|
Ok(n) => valid_args
|
||||||
|
.push(ArgOptions::Mapping((SpecialCharacterIndices::VTIME, n))),
|
||||||
|
Err(e) => return Err(USimpleError::new(1, e)),
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
return Err(USimpleError::new(
|
||||||
|
1,
|
||||||
|
get_message_with_args(
|
||||||
|
"stty-error-missing-argument",
|
||||||
|
HashMap::from([("arg".to_string(), arg.to_string())]),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// baud rate setting
|
||||||
} else if let Some(baud_flag) = string_to_baud(arg) {
|
} else if let Some(baud_flag) = string_to_baud(arg) {
|
||||||
valid_args.push(ArgOptions::Flags(baud_flag));
|
valid_args.push(ArgOptions::Flags(baud_flag));
|
||||||
// non control char flag
|
// non control char flag
|
||||||
|
@ -365,6 +421,10 @@ fn stty(opts: &Options) -> UResult<()> {
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
} else if *arg == "drain" {
|
||||||
|
set_arg = SetArg::TCSADRAIN;
|
||||||
|
} else if *arg == "-drain" {
|
||||||
|
set_arg = SetArg::TCSANOW;
|
||||||
} else if *arg == "size" {
|
} else if *arg == "size" {
|
||||||
valid_args.push(ArgOptions::Print(PrintSetting::Size));
|
valid_args.push(ArgOptions::Print(PrintSetting::Size));
|
||||||
// not a valid option
|
// not a valid option
|
||||||
|
@ -388,19 +448,15 @@ fn stty(opts: &Options) -> UResult<()> {
|
||||||
ArgOptions::Mapping(mapping) => apply_char_mapping(&mut termios, mapping),
|
ArgOptions::Mapping(mapping) => apply_char_mapping(&mut termios, mapping),
|
||||||
ArgOptions::Flags(flag) => apply_setting(&mut termios, flag),
|
ArgOptions::Flags(flag) => apply_setting(&mut termios, flag),
|
||||||
ArgOptions::Special(setting) => {
|
ArgOptions::Special(setting) => {
|
||||||
apply_special_setting(setting, opts.file.as_raw_fd())?;
|
apply_special_setting(&mut termios, setting, opts.file.as_raw_fd())?;
|
||||||
}
|
}
|
||||||
ArgOptions::Print(setting) => {
|
ArgOptions::Print(setting) => {
|
||||||
print_special_setting(setting, opts.file.as_raw_fd())?;
|
print_special_setting(setting, opts.file.as_raw_fd())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tcsetattr(
|
tcsetattr(opts.file.as_fd(), set_arg, &termios)
|
||||||
opts.file.as_fd(),
|
.expect("Could not write terminal attributes");
|
||||||
nix::sys::termios::SetArg::TCSANOW,
|
|
||||||
&termios,
|
|
||||||
)
|
|
||||||
.expect("Could not write terminal attributes");
|
|
||||||
} else {
|
} else {
|
||||||
// TODO: Figure out the right error message for when tcgetattr fails
|
// TODO: Figure out the right error message for when tcgetattr fails
|
||||||
let termios = tcgetattr(opts.file.as_fd()).expect("Could not get terminal attributes");
|
let termios = tcgetattr(opts.file.as_fd()).expect("Could not get terminal attributes");
|
||||||
|
@ -409,6 +465,21 @@ fn stty(opts: &Options) -> UResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GNU uses different error messages if values overflow or underflow a u8
|
||||||
|
// this function returns the appropriate error message in the case of overflow or underflow, or u8 on success
|
||||||
|
fn parse_u8_or_err(arg: &str) -> Result<u8, String> {
|
||||||
|
arg.parse::<u8>().map_err(|e| match e.kind() {
|
||||||
|
IntErrorKind::PosOverflow => get_message_with_args(
|
||||||
|
"stty-error-invalid-integer-argument-value-too-large",
|
||||||
|
HashMap::from([("value".to_string(), format!("'{}'", arg))]),
|
||||||
|
),
|
||||||
|
_ => get_message_with_args(
|
||||||
|
"stty-error-invalid-integer-argument",
|
||||||
|
HashMap::from([("value".to_string(), format!("'{}'", arg))]),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// GNU uses an unsigned 32 bit integer for row/col sizes, but then wraps around 16 bits
|
// GNU uses an unsigned 32 bit integer for row/col sizes, but then wraps around 16 bits
|
||||||
// this function returns Some(n), where n is a u16 row/col size, or None if the string arg cannot be parsed as a u32
|
// this function returns Some(n), where n is a u16 row/col size, or None if the string arg cannot be parsed as a u32
|
||||||
fn parse_rows_cols(arg: &str) -> Option<u16> {
|
fn parse_rows_cols(arg: &str) -> Option<u16> {
|
||||||
|
@ -745,12 +816,23 @@ fn apply_char_mapping(termios: &mut Termios, mapping: &(SpecialCharacterIndices,
|
||||||
termios.control_chars[mapping.0 as usize] = mapping.1;
|
termios.control_chars[mapping.0 as usize] = mapping.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_special_setting(setting: &SpecialSetting, fd: i32) -> nix::Result<()> {
|
fn apply_special_setting(
|
||||||
|
_termios: &mut Termios,
|
||||||
|
setting: &SpecialSetting,
|
||||||
|
fd: i32,
|
||||||
|
) -> nix::Result<()> {
|
||||||
let mut size = TermSize::default();
|
let mut size = TermSize::default();
|
||||||
unsafe { tiocgwinsz(fd, &raw mut size)? };
|
unsafe { tiocgwinsz(fd, &raw mut size)? };
|
||||||
match setting {
|
match setting {
|
||||||
SpecialSetting::Rows(n) => size.rows = *n,
|
SpecialSetting::Rows(n) => size.rows = *n,
|
||||||
SpecialSetting::Cols(n) => size.columns = *n,
|
SpecialSetting::Cols(n) => size.columns = *n,
|
||||||
|
SpecialSetting::Line(_n) => {
|
||||||
|
// nix only defines Termios's `line_discipline` field on these platforms
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
|
{
|
||||||
|
_termios.line_discipline = *_n;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
unsafe { tiocswinsz(fd, &raw mut size)? };
|
unsafe { tiocswinsz(fd, &raw mut size)? };
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -241,3 +241,55 @@ fn row_column_sizes() {
|
||||||
.fails()
|
.fails()
|
||||||
.stderr_contains("missing argument to 'rows'");
|
.stderr_contains("missing argument to 'rows'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
|
fn line() {
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["line"])
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("missing argument to 'line'");
|
||||||
|
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["line", "-1"])
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("invalid integer argument: '-1'");
|
||||||
|
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["line", "256"])
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("invalid integer argument: '256'");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn min_and_time() {
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["min"])
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("missing argument to 'min'");
|
||||||
|
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["time"])
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("missing argument to 'time'");
|
||||||
|
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["min", "-1"])
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("invalid integer argument: '-1'");
|
||||||
|
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["time", "-1"])
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("invalid integer argument: '-1'");
|
||||||
|
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["min", "256"])
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("invalid integer argument: '256': Value too large for defined data type");
|
||||||
|
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["time", "256"])
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("invalid integer argument: '256': Value too large for defined data type");
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue