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
13+ // spell-checker:ignore cbreak decctlq evenp litout oddp tcsadrain exta extb
1414
1515mod flags;
1616
@@ -23,6 +23,7 @@ use nix::sys::termios::{
2323 Termios , cfgetospeed, cfsetospeed, tcgetattr, tcsetattr,
2424} ;
2525use nix:: { ioctl_read_bad, ioctl_write_ptr_bad} ;
26+ use std:: cmp:: Ordering ;
2627use std:: fs:: File ;
2728use std:: io:: { self , Stdout , stdout} ;
2829use std:: num:: IntErrorKind ;
@@ -563,7 +564,69 @@ fn string_to_combo(arg: &str) -> Option<&str> {
563564 . map ( |_| arg)
564565}
565566
567+ /// Parse and round a baud rate value using GNU stty's custom rounding algorithm.
568+ ///
569+ /// Accepts decimal values with the following rounding rules:
570+ /// - If first digit after decimal > 5: round up
571+ /// - If first digit after decimal < 5: round down
572+ /// - If first digit after decimal == 5:
573+ /// - If followed by any non-zero digit: round up
574+ /// - If followed only by zeros (or nothing): banker's rounding (round to nearest even)
575+ ///
576+ /// Examples: "9600.49" -> 9600, "9600.51" -> 9600, "9600.5" -> 9600 (even), "9601.5" -> 9602 (even)
577+ /// TODO: there are two special cases "exta" → B19200 and "extb" → B38400
578+ fn parse_baud_with_rounding ( normalized : & str ) -> Option < u32 > {
579+ let ( int_part, frac_part) = match normalized. split_once ( '.' ) {
580+ Some ( ( i, f) ) => ( i, Some ( f) ) ,
581+ None => ( normalized, None ) ,
582+ } ;
583+
584+ let mut value = int_part. parse :: < u32 > ( ) . ok ( ) ?;
585+
586+ if let Some ( frac) = frac_part {
587+ let mut chars = frac. chars ( ) ;
588+ let first_digit = chars. next ( ) ?. to_digit ( 10 ) ?;
589+
590+ // Validate all remaining chars are digits
591+ let rest: Vec < _ > = chars. collect ( ) ;
592+ if !rest. iter ( ) . all ( |c| c. is_ascii_digit ( ) ) {
593+ return None ;
594+ }
595+
596+ match first_digit. cmp ( & 5 ) {
597+ Ordering :: Greater => value += 1 ,
598+ Ordering :: Equal => {
599+ // Check if any non-zero digit follows
600+ if rest. iter ( ) . any ( |& c| c != '0' ) {
601+ value += 1 ;
602+ } else {
603+ // Banker's rounding: round to nearest even
604+ value += value & 1 ;
605+ }
606+ }
607+ Ordering :: Less => { } // Round down, already validated
608+ }
609+ }
610+
611+ Some ( value)
612+ }
613+
566614fn string_to_baud ( arg : & str ) -> Option < AllFlags < ' _ > > {
615+ // Reject invalid formats
616+ if arg != arg. trim_end ( )
617+ || arg. trim ( ) . starts_with ( '-' )
618+ || arg. trim ( ) . starts_with ( "++" )
619+ || arg. contains ( 'E' )
620+ || arg. contains ( 'e' )
621+ || arg. matches ( '.' ) . count ( ) > 1
622+ {
623+ return None ;
624+ }
625+
626+ let normalized = arg. trim ( ) . trim_start_matches ( '+' ) ;
627+ let normalized = normalized. strip_suffix ( '.' ) . unwrap_or ( normalized) ;
628+ let value = parse_baud_with_rounding ( normalized) ?;
629+
567630 // BSDs use a u32 for the baud rate, so any decimal number applies.
568631 #[ cfg( any(
569632 target_os = "freebsd" ,
@@ -573,9 +636,7 @@ fn string_to_baud(arg: &str) -> Option<AllFlags<'_>> {
573636 target_os = "netbsd" ,
574637 target_os = "openbsd"
575638 ) ) ]
576- if let Ok ( n) = arg. parse :: < u32 > ( ) {
577- return Some ( AllFlags :: Baud ( n) ) ;
578- }
639+ return Some ( AllFlags :: Baud ( value) ) ;
579640
580641 #[ cfg( not( any(
581642 target_os = "freebsd" ,
@@ -585,12 +646,14 @@ fn string_to_baud(arg: &str) -> Option<AllFlags<'_>> {
585646 target_os = "netbsd" ,
586647 target_os = "openbsd"
587648 ) ) ) ]
588- for ( text, baud_rate) in BAUD_RATES {
589- if * text == arg {
590- return Some ( AllFlags :: Baud ( * baud_rate) ) ;
649+ {
650+ for ( text, baud_rate) in BAUD_RATES {
651+ if text. parse :: < u32 > ( ) . ok ( ) == Some ( value) {
652+ return Some ( AllFlags :: Baud ( * baud_rate) ) ;
653+ }
591654 }
655+ None
592656 }
593- None
594657}
595658
596659/// return `Some(flag)` if the input is a valid flag, `None` if not
0 commit comments