Skip to content

Commit d737450

Browse files
authored
Stty: Implemented input and output baud rate setting for stty (#9517)
1 parent e143680 commit d737450

File tree

3 files changed

+135
-59
lines changed

3 files changed

+135
-59
lines changed

src/uu/stty/src/flags.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ use nix::sys::termios::{
2727
SpecialCharacterIndices as S,
2828
};
2929

30+
#[derive(Debug)]
31+
#[cfg_attr(test, derive(PartialEq))]
32+
pub enum BaudType {
33+
Input,
34+
Output,
35+
Both,
36+
}
37+
3038
#[derive(Debug)]
3139
#[cfg_attr(test, derive(PartialEq))]
3240
pub enum AllFlags<'a> {
@@ -38,7 +46,7 @@ pub enum AllFlags<'a> {
3846
target_os = "netbsd",
3947
target_os = "openbsd"
4048
))]
41-
Baud(u32),
49+
Baud(u32, BaudType),
4250
#[cfg(not(any(
4351
target_os = "freebsd",
4452
target_os = "dragonfly",
@@ -47,7 +55,7 @@ pub enum AllFlags<'a> {
4755
target_os = "netbsd",
4856
target_os = "openbsd"
4957
)))]
50-
Baud(BaudRate),
58+
Baud(BaudRate, BaudType),
5159
ControlFlags((&'a Flag<C>, bool)),
5260
InputFlags((&'a Flag<I>, bool)),
5361
LocalFlags((&'a Flag<L>, bool)),

src/uu/stty/src/stty.rs

Lines changed: 60 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
// spell-checker:ignore isig icanon iexten echoe crterase echok echonl noflsh xcase tostop echoprt prterase echoctl ctlecho echoke crtkill flusho extproc
1111
// spell-checker:ignore lnext rprnt susp swtch vdiscard veof veol verase vintr vkill vlnext vquit vreprint vstart vstop vsusp vswtc vwerase werase
1212
// spell-checker:ignore sigquit sigtstp
13-
// spell-checker:ignore cbreak decctlq evenp litout oddp tcsadrain exta extb NCCS
13+
// spell-checker:ignore cbreak decctlq evenp litout oddp tcsadrain exta extb NCCS cfsetispeed
1414
// spell-checker:ignore notaflag notacombo notabaud
1515

1616
mod flags;
@@ -21,7 +21,7 @@ use clap::{Arg, ArgAction, ArgMatches, Command};
2121
use nix::libc::{O_NONBLOCK, TIOCGWINSZ, TIOCSWINSZ, c_ushort};
2222
use nix::sys::termios::{
2323
ControlFlags, InputFlags, LocalFlags, OutputFlags, SetArg, SpecialCharacterIndices as S,
24-
Termios, cfgetospeed, cfsetospeed, tcgetattr, tcsetattr,
24+
Termios, cfgetospeed, cfsetispeed, cfsetospeed, tcgetattr, tcsetattr,
2525
};
2626
use nix::{ioctl_read_bad, ioctl_write_ptr_bad};
2727
use std::cmp::Ordering;
@@ -274,19 +274,24 @@ fn stty(opts: &Options) -> UResult<()> {
274274
let mut args_iter = args.iter();
275275
while let Some(&arg) = args_iter.next() {
276276
match arg {
277-
"ispeed" | "ospeed" => match args_iter.next() {
277+
"ispeed" => match args_iter.next() {
278278
Some(speed) => {
279-
if let Some(baud_flag) = string_to_baud(speed) {
279+
if let Some(baud_flag) = string_to_baud(speed, flags::BaudType::Input) {
280280
valid_args.push(ArgOptions::Flags(baud_flag));
281281
} else {
282-
return Err(USimpleError::new(
283-
1,
284-
translate!(
285-
"stty-error-invalid-speed",
286-
"arg" => *arg,
287-
"speed" => *speed,
288-
),
289-
));
282+
return invalid_speed(arg, speed);
283+
}
284+
}
285+
None => {
286+
return missing_arg(arg);
287+
}
288+
},
289+
"ospeed" => match args_iter.next() {
290+
Some(speed) => {
291+
if let Some(baud_flag) = string_to_baud(speed, flags::BaudType::Output) {
292+
valid_args.push(ArgOptions::Flags(baud_flag));
293+
} else {
294+
return invalid_speed(arg, speed);
290295
}
291296
}
292297
None => {
@@ -383,12 +388,12 @@ fn stty(opts: &Options) -> UResult<()> {
383388
return missing_arg(arg);
384389
}
385390
// baud rate
386-
} else if let Some(baud_flag) = string_to_baud(arg) {
391+
} else if let Some(baud_flag) = string_to_baud(arg, flags::BaudType::Both) {
387392
valid_args.push(ArgOptions::Flags(baud_flag));
388393
// non control char flag
389394
} else if let Some(flag) = string_to_flag(arg) {
390395
let remove_group = match flag {
391-
AllFlags::Baud(_) => false,
396+
AllFlags::Baud(_, _) => false,
392397
AllFlags::ControlFlags((flag, remove)) => {
393398
check_flag_group(flag, remove)
394399
}
@@ -417,7 +422,7 @@ fn stty(opts: &Options) -> UResult<()> {
417422
for arg in &valid_args {
418423
match arg {
419424
ArgOptions::Mapping(mapping) => apply_char_mapping(&mut termios, mapping),
420-
ArgOptions::Flags(flag) => apply_setting(&mut termios, flag),
425+
ArgOptions::Flags(flag) => apply_setting(&mut termios, flag)?,
421426
ArgOptions::Special(setting) => {
422427
apply_special_setting(&mut termios, setting, opts.file.as_raw_fd())?;
423428
}
@@ -468,6 +473,17 @@ fn invalid_integer_arg<T>(arg: &str) -> Result<T, Box<dyn UError>> {
468473
))
469474
}
470475

476+
fn invalid_speed<T>(arg: &str, speed: &str) -> Result<T, Box<dyn UError>> {
477+
Err(UUsageError::new(
478+
1,
479+
translate!(
480+
"stty-error-invalid-speed",
481+
"arg" => arg,
482+
"speed" => speed,
483+
),
484+
))
485+
}
486+
471487
/// GNU uses different error messages if values overflow or underflow a u8,
472488
/// this function returns the appropriate error message in the case of overflow or underflow, or u8 on success
473489
fn parse_u8_or_err(arg: &str) -> Result<u8, String> {
@@ -719,7 +735,7 @@ fn parse_baud_with_rounding(normalized: &str) -> Option<u32> {
719735
Some(value)
720736
}
721737

722-
fn string_to_baud(arg: &str) -> Option<AllFlags<'_>> {
738+
fn string_to_baud(arg: &str, baud_type: flags::BaudType) -> Option<AllFlags<'_>> {
723739
// Reject invalid formats
724740
if arg != arg.trim_end()
725741
|| arg.trim().starts_with('-')
@@ -744,7 +760,7 @@ fn string_to_baud(arg: &str) -> Option<AllFlags<'_>> {
744760
target_os = "netbsd",
745761
target_os = "openbsd"
746762
))]
747-
return Some(AllFlags::Baud(value));
763+
return Some(AllFlags::Baud(value, baud_type));
748764

749765
#[cfg(not(any(
750766
target_os = "freebsd",
@@ -757,7 +773,7 @@ fn string_to_baud(arg: &str) -> Option<AllFlags<'_>> {
757773
{
758774
for (text, baud_rate) in BAUD_RATES {
759775
if text.parse::<u32>().ok() == Some(value) {
760-
return Some(AllFlags::Baud(*baud_rate));
776+
return Some(AllFlags::Baud(*baud_rate, baud_type));
761777
}
762778
}
763779
None
@@ -940,9 +956,9 @@ fn print_flags<T: TermiosFlag>(
940956
}
941957

942958
/// Apply a single setting
943-
fn apply_setting(termios: &mut Termios, setting: &AllFlags) {
959+
fn apply_setting(termios: &mut Termios, setting: &AllFlags) -> nix::Result<()> {
944960
match setting {
945-
AllFlags::Baud(_) => apply_baud_rate_flag(termios, setting),
961+
AllFlags::Baud(_, _) => apply_baud_rate_flag(termios, setting)?,
946962
AllFlags::ControlFlags((setting, disable)) => {
947963
setting.flag.apply(termios, !disable);
948964
}
@@ -956,34 +972,21 @@ fn apply_setting(termios: &mut Termios, setting: &AllFlags) {
956972
setting.flag.apply(termios, !disable);
957973
}
958974
}
975+
Ok(())
959976
}
960977

961-
fn apply_baud_rate_flag(termios: &mut Termios, input: &AllFlags) {
962-
// BSDs use a u32 for the baud rate, so any decimal number applies.
963-
#[cfg(any(
964-
target_os = "freebsd",
965-
target_os = "dragonfly",
966-
target_os = "ios",
967-
target_os = "macos",
968-
target_os = "netbsd",
969-
target_os = "openbsd"
970-
))]
971-
if let AllFlags::Baud(n) = input {
972-
cfsetospeed(termios, *n).expect("Failed to set baud rate");
973-
}
974-
975-
// Other platforms use an enum.
976-
#[cfg(not(any(
977-
target_os = "freebsd",
978-
target_os = "dragonfly",
979-
target_os = "ios",
980-
target_os = "macos",
981-
target_os = "netbsd",
982-
target_os = "openbsd"
983-
)))]
984-
if let AllFlags::Baud(br) = input {
985-
cfsetospeed(termios, *br).expect("Failed to set baud rate");
978+
fn apply_baud_rate_flag(termios: &mut Termios, input: &AllFlags) -> nix::Result<()> {
979+
if let AllFlags::Baud(rate, baud_type) = input {
980+
match baud_type {
981+
flags::BaudType::Input => cfsetispeed(termios, *rate)?,
982+
flags::BaudType::Output => cfsetospeed(termios, *rate)?,
983+
flags::BaudType::Both => {
984+
cfsetispeed(termios, *rate)?;
985+
cfsetospeed(termios, *rate)?;
986+
}
987+
}
986988
}
989+
Ok(())
987990
}
988991

989992
fn apply_char_mapping(termios: &mut Termios, mapping: &(S, u8)) {
@@ -1446,10 +1449,10 @@ mod tests {
14461449
target_os = "openbsd"
14471450
)))]
14481451
{
1449-
assert!(string_to_baud("9600").is_some());
1450-
assert!(string_to_baud("115200").is_some());
1451-
assert!(string_to_baud("38400").is_some());
1452-
assert!(string_to_baud("19200").is_some());
1452+
assert!(string_to_baud("9600", flags::BaudType::Both).is_some());
1453+
assert!(string_to_baud("115200", flags::BaudType::Both).is_some());
1454+
assert!(string_to_baud("38400", flags::BaudType::Both).is_some());
1455+
assert!(string_to_baud("19200", flags::BaudType::Both).is_some());
14531456
}
14541457

14551458
#[cfg(any(
@@ -1461,10 +1464,10 @@ mod tests {
14611464
target_os = "openbsd"
14621465
))]
14631466
{
1464-
assert!(string_to_baud("9600").is_some());
1465-
assert!(string_to_baud("115200").is_some());
1466-
assert!(string_to_baud("1000000").is_some());
1467-
assert!(string_to_baud("0").is_some());
1467+
assert!(string_to_baud("9600", flags::BaudType::Both).is_some());
1468+
assert!(string_to_baud("115200", flags::BaudType::Both).is_some());
1469+
assert!(string_to_baud("1000000", flags::BaudType::Both).is_some());
1470+
assert!(string_to_baud("0", flags::BaudType::Both).is_some());
14681471
}
14691472
}
14701473

@@ -1479,10 +1482,10 @@ mod tests {
14791482
target_os = "openbsd"
14801483
)))]
14811484
{
1482-
assert_eq!(string_to_baud("995"), None);
1483-
assert_eq!(string_to_baud("invalid"), None);
1484-
assert_eq!(string_to_baud(""), None);
1485-
assert_eq!(string_to_baud("abc"), None);
1485+
assert_eq!(string_to_baud("995", flags::BaudType::Both), None);
1486+
assert_eq!(string_to_baud("invalid", flags::BaudType::Both), None);
1487+
assert_eq!(string_to_baud("", flags::BaudType::Both), None);
1488+
assert_eq!(string_to_baud("abc", flags::BaudType::Both), None);
14861489
}
14871490
}
14881491

tests/by-util/test_stty.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1627,6 +1627,71 @@ fn test_stty_uses_stdin() {
16271627
.stdout_contains("columns 100");
16281628
}
16291629

1630+
#[test]
1631+
#[cfg(unix)]
1632+
fn test_ispeed_ospeed_valid_speeds() {
1633+
let (path, _controller, _replica) = pty_path();
1634+
let (_at, ts) = at_and_ts!();
1635+
1636+
// Test various valid baud rates for both ispeed and ospeed
1637+
let test_cases = [
1638+
("ispeed", "50"),
1639+
("ispeed", "9600"),
1640+
("ispeed", "19200"),
1641+
("ospeed", "1200"),
1642+
("ospeed", "9600"),
1643+
("ospeed", "38400"),
1644+
];
1645+
1646+
for (arg, speed) in test_cases {
1647+
let result = ts.ucmd().args(&["--file", &path, arg, speed]).run();
1648+
let exp_result = unwrap_or_return!(expected_result(&ts, &["--file", &path, arg, speed]));
1649+
let normalized_stderr = normalize_stderr(result.stderr_str());
1650+
1651+
result
1652+
.stdout_is(exp_result.stdout_str())
1653+
.code_is(exp_result.code());
1654+
assert_eq!(normalized_stderr, exp_result.stderr_str());
1655+
}
1656+
}
1657+
1658+
#[test]
1659+
#[cfg(all(
1660+
unix,
1661+
not(any(
1662+
target_os = "freebsd",
1663+
target_os = "dragonfly",
1664+
target_os = "ios",
1665+
target_os = "macos",
1666+
target_os = "netbsd",
1667+
target_os = "openbsd"
1668+
))
1669+
))]
1670+
#[ignore = "Issue: #9547"]
1671+
fn test_ispeed_ospeed_invalid_speeds() {
1672+
let (path, _controller, _replica) = pty_path();
1673+
let (_at, ts) = at_and_ts!();
1674+
1675+
// Test invalid speed values (non-standard baud rates)
1676+
let test_cases = [
1677+
("ispeed", "12345"),
1678+
("ospeed", "99999"),
1679+
("ispeed", "abc"),
1680+
("ospeed", "xyz"),
1681+
];
1682+
1683+
for (arg, speed) in test_cases {
1684+
let result = ts.ucmd().args(&["--file", &path, arg, speed]).run();
1685+
let exp_result = unwrap_or_return!(expected_result(&ts, &["--file", &path, arg, speed]));
1686+
let normalized_stderr = normalize_stderr(result.stderr_str());
1687+
1688+
result
1689+
.stdout_is(exp_result.stdout_str())
1690+
.code_is(exp_result.code());
1691+
assert_eq!(normalized_stderr, exp_result.stderr_str());
1692+
}
1693+
}
1694+
16301695
#[test]
16311696
#[cfg(unix)]
16321697
fn test_columns_env_wrapping() {

0 commit comments

Comments
 (0)