Skip to content

Commit a1c0044

Browse files
committed
Implemented input and output baud rate setting for stty
1 parent b8edd13 commit a1c0044

File tree

3 files changed

+129
-27
lines changed

3 files changed

+129
-27
lines changed

src/uu/stty/src/flags.rs

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

30+
pub enum BaudType {
31+
Input,
32+
Output,
33+
Both,
34+
}
35+
3036
pub enum AllFlags<'a> {
3137
#[cfg(any(
3238
target_os = "freebsd",
@@ -36,7 +42,7 @@ pub enum AllFlags<'a> {
3642
target_os = "netbsd",
3743
target_os = "openbsd"
3844
))]
39-
Baud(u32),
45+
Baud(u32, BaudType),
4046
#[cfg(not(any(
4147
target_os = "freebsd",
4248
target_os = "dragonfly",
@@ -45,7 +51,7 @@ pub enum AllFlags<'a> {
4551
target_os = "netbsd",
4652
target_os = "openbsd"
4753
)))]
48-
Baud(BaudRate),
54+
Baud(BaudRate, BaudType),
4955
ControlFlags((&'a Flag<C>, bool)),
5056
InputFlags((&'a Flag<I>, bool)),
5157
LocalFlags((&'a Flag<L>, bool)),

src/uu/stty/src/stty.rs

Lines changed: 57 additions & 25 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

1515
mod flags;
1616

@@ -20,7 +20,7 @@ use clap::{Arg, ArgAction, ArgMatches, Command};
2020
use nix::libc::{O_NONBLOCK, TIOCGWINSZ, TIOCSWINSZ, c_ushort};
2121
use nix::sys::termios::{
2222
ControlFlags, InputFlags, LocalFlags, OutputFlags, SetArg, SpecialCharacterIndices as S,
23-
Termios, cfgetospeed, cfsetospeed, tcgetattr, tcsetattr,
23+
Termios, cfgetospeed, cfsetispeed, cfsetospeed, tcgetattr, tcsetattr,
2424
};
2525
use nix::{ioctl_read_bad, ioctl_write_ptr_bad};
2626
use std::cmp::Ordering;
@@ -273,19 +273,24 @@ fn stty(opts: &Options) -> UResult<()> {
273273
let mut args_iter = args.iter();
274274
while let Some(&arg) = args_iter.next() {
275275
match arg {
276-
"ispeed" | "ospeed" => match args_iter.next() {
276+
"ispeed" => match args_iter.next() {
277277
Some(speed) => {
278-
if let Some(baud_flag) = string_to_baud(speed) {
278+
if let Some(baud_flag) = string_to_baud(speed, flags::BaudType::Input) {
279279
valid_args.push(ArgOptions::Flags(baud_flag));
280280
} else {
281-
return Err(USimpleError::new(
282-
1,
283-
translate!(
284-
"stty-error-invalid-speed",
285-
"arg" => *arg,
286-
"speed" => *speed,
287-
),
288-
));
281+
return invalid_speed(arg, speed);
282+
}
283+
}
284+
None => {
285+
return missing_arg(arg);
286+
}
287+
},
288+
"ospeed" => match args_iter.next() {
289+
Some(speed) => {
290+
if let Some(baud_flag) = string_to_baud(speed, flags::BaudType::Output) {
291+
valid_args.push(ArgOptions::Flags(baud_flag));
292+
} else {
293+
return invalid_speed(arg, speed);
289294
}
290295
}
291296
None => {
@@ -382,12 +387,12 @@ fn stty(opts: &Options) -> UResult<()> {
382387
return missing_arg(arg);
383388
}
384389
// baud rate
385-
} else if let Some(baud_flag) = string_to_baud(arg) {
390+
} else if let Some(baud_flag) = string_to_baud(arg, flags::BaudType::Both) {
386391
valid_args.push(ArgOptions::Flags(baud_flag));
387392
// non control char flag
388393
} else if let Some(flag) = string_to_flag(arg) {
389394
let remove_group = match flag {
390-
AllFlags::Baud(_) => false,
395+
AllFlags::Baud(_, _) => false,
391396
AllFlags::ControlFlags((flag, remove)) => {
392397
check_flag_group(flag, remove)
393398
}
@@ -416,7 +421,7 @@ fn stty(opts: &Options) -> UResult<()> {
416421
for arg in &valid_args {
417422
match arg {
418423
ArgOptions::Mapping(mapping) => apply_char_mapping(&mut termios, mapping),
419-
ArgOptions::Flags(flag) => apply_setting(&mut termios, flag),
424+
ArgOptions::Flags(flag) => apply_setting(&mut termios, flag)?,
420425
ArgOptions::Special(setting) => {
421426
apply_special_setting(&mut termios, setting, opts.file.as_raw_fd())?;
422427
}
@@ -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> {
@@ -657,7 +673,7 @@ fn parse_baud_with_rounding(normalized: &str) -> Option<u32> {
657673
Some(value)
658674
}
659675

660-
fn string_to_baud(arg: &str) -> Option<AllFlags<'_>> {
676+
fn string_to_baud(arg: &str, baud_type: flags::BaudType) -> Option<AllFlags<'_>> {
661677
// Reject invalid formats
662678
if arg != arg.trim_end()
663679
|| arg.trim().starts_with('-')
@@ -682,7 +698,7 @@ fn string_to_baud(arg: &str) -> Option<AllFlags<'_>> {
682698
target_os = "netbsd",
683699
target_os = "openbsd"
684700
))]
685-
return Some(AllFlags::Baud(value));
701+
return Some(AllFlags::Baud(value, baud_type));
686702

687703
#[cfg(not(any(
688704
target_os = "freebsd",
@@ -695,7 +711,7 @@ fn string_to_baud(arg: &str) -> Option<AllFlags<'_>> {
695711
{
696712
for (text, baud_rate) in BAUD_RATES {
697713
if text.parse::<u32>().ok() == Some(value) {
698-
return Some(AllFlags::Baud(*baud_rate));
714+
return Some(AllFlags::Baud(*baud_rate, baud_type));
699715
}
700716
}
701717
None
@@ -853,9 +869,9 @@ fn print_flags<T: TermiosFlag>(termios: &Termios, opts: &Options, flags: &[Flag<
853869
}
854870

855871
/// Apply a single setting
856-
fn apply_setting(termios: &mut Termios, setting: &AllFlags) {
872+
fn apply_setting(termios: &mut Termios, setting: &AllFlags) -> nix::Result<()> {
857873
match setting {
858-
AllFlags::Baud(_) => apply_baud_rate_flag(termios, setting),
874+
AllFlags::Baud(_, _) => apply_baud_rate_flag(termios, setting)?,
859875
AllFlags::ControlFlags((setting, disable)) => {
860876
setting.flag.apply(termios, !disable);
861877
}
@@ -869,9 +885,10 @@ fn apply_setting(termios: &mut Termios, setting: &AllFlags) {
869885
setting.flag.apply(termios, !disable);
870886
}
871887
}
888+
Ok(())
872889
}
873890

874-
fn apply_baud_rate_flag(termios: &mut Termios, input: &AllFlags) {
891+
fn apply_baud_rate_flag(termios: &mut Termios, input: &AllFlags) -> nix::Result<()> {
875892
// BSDs use a u32 for the baud rate, so any decimal number applies.
876893
#[cfg(any(
877894
target_os = "freebsd",
@@ -881,8 +898,15 @@ fn apply_baud_rate_flag(termios: &mut Termios, input: &AllFlags) {
881898
target_os = "netbsd",
882899
target_os = "openbsd"
883900
))]
884-
if let AllFlags::Baud(n) = input {
885-
cfsetospeed(termios, *n).expect("Failed to set baud rate");
901+
if let AllFlags::Baud(n, baud_type) = input {
902+
match baud_type {
903+
flags::BaudType::Input => cfsetispeed(termios, *n)?,
904+
flags::BaudType::Output => cfsetospeed(termios, *n)?,
905+
flags::BaudType::Both => {
906+
cfsetispeed(termios, *n)?;
907+
cfsetospeed(termios, *n)?;
908+
}
909+
}
886910
}
887911

888912
// Other platforms use an enum.
@@ -894,9 +918,17 @@ fn apply_baud_rate_flag(termios: &mut Termios, input: &AllFlags) {
894918
target_os = "netbsd",
895919
target_os = "openbsd"
896920
)))]
897-
if let AllFlags::Baud(br) = input {
898-
cfsetospeed(termios, *br).expect("Failed to set baud rate");
921+
if let AllFlags::Baud(br, baud_type) = input {
922+
match baud_type {
923+
flags::BaudType::Input => cfsetispeed(termios, *br)?,
924+
flags::BaudType::Output => cfsetospeed(termios, *br)?,
925+
flags::BaudType::Both => {
926+
cfsetispeed(termios, *br)?;
927+
cfsetospeed(termios, *br)?;
928+
}
929+
}
899930
}
931+
Ok(())
900932
}
901933

902934
fn apply_char_mapping(termios: &mut Termios, mapping: &(S, u8)) {

tests/by-util/test_stty.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,3 +526,67 @@ fn test_saved_state_with_control_chars() {
526526
.stderr_is(exp_result.stderr_str())
527527
.code_is(exp_result.code());
528528
}
529+
530+
#[test]
531+
#[cfg(unix)]
532+
fn test_ispeed_ospeed_valid_speeds() {
533+
let (path, _controller, _replica) = pty_path();
534+
let (_at, ts) = at_and_ts!();
535+
536+
// Test various valid baud rates for both ispeed and ospeed
537+
let test_cases = [
538+
("ispeed", "50"),
539+
("ispeed", "9600"),
540+
("ispeed", "19200"),
541+
("ospeed", "1200"),
542+
("ospeed", "9600"),
543+
("ospeed", "38400"),
544+
];
545+
546+
for (arg, speed) in test_cases {
547+
let result = ts.ucmd().args(&["--file", &path, arg, speed]).run();
548+
let exp_result = unwrap_or_return!(expected_result(&ts, &["--file", &path, arg, speed]));
549+
let normalized_stderr = normalize_stderr(result.stderr_str());
550+
551+
result
552+
.stdout_is(exp_result.stdout_str())
553+
.code_is(exp_result.code());
554+
assert_eq!(normalized_stderr, exp_result.stderr_str());
555+
}
556+
}
557+
558+
#[test]
559+
#[cfg(all(
560+
unix,
561+
not(any(
562+
target_os = "freebsd",
563+
target_os = "dragonfly",
564+
target_os = "ios",
565+
target_os = "macos",
566+
target_os = "netbsd",
567+
target_os = "openbsd"
568+
))
569+
))]
570+
fn test_ispeed_ospeed_invalid_speeds() {
571+
let (path, _controller, _replica) = pty_path();
572+
let (_at, ts) = at_and_ts!();
573+
574+
// Test invalid speed values (non-standard baud rates)
575+
let test_cases = [
576+
("ispeed", "12345"),
577+
("ospeed", "99999"),
578+
("ispeed", "abc"),
579+
("ospeed", "xyz"),
580+
];
581+
582+
for (arg, speed) in test_cases {
583+
let result = ts.ucmd().args(&["--file", &path, arg, speed]).run();
584+
let exp_result = unwrap_or_return!(expected_result(&ts, &["--file", &path, arg, speed]));
585+
let normalized_stderr = normalize_stderr(result.stderr_str());
586+
587+
result
588+
.stdout_is(exp_result.stdout_str())
589+
.code_is(exp_result.code());
590+
assert_eq!(normalized_stderr, exp_result.stderr_str());
591+
}
592+
}

0 commit comments

Comments
 (0)