-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
stty: Improve option handling and align behavior with GNU coreutils #9255
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 15 commits
0dd8afb
7e19f74
2485911
6c1d363
8ccd314
17abac4
8c84686
7fd9246
767a74f
1754286
582470a
6d1f375
0afdf35
4f4ec2c
24fa079
7aed8d4
5d30e89
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,7 +10,7 @@ | |
| // spell-checker:ignore isig icanon iexten echoe crterase echok echonl noflsh xcase tostop echoprt prterase echoctl ctlecho echoke crtkill flusho extproc | ||
| // spell-checker:ignore lnext rprnt susp swtch vdiscard veof veol verase vintr vkill vlnext vquit vreprint vstart vstop vsusp vswtc vwerase werase | ||
| // spell-checker:ignore sigquit sigtstp | ||
| // spell-checker:ignore cbreak decctlq evenp litout oddp tcsadrain | ||
| // spell-checker:ignore cbreak decctlq evenp litout oddp tcsadrain tcflag bitflag lflags cflags | ||
|
|
||
| mod flags; | ||
|
|
||
|
|
@@ -211,7 +211,7 @@ impl<'a> Options<'a> { | |
| }, | ||
| settings: matches | ||
| .get_many::<String>(options::SETTINGS) | ||
| .map(|v| v.map(|s| s.as_ref()).collect()), | ||
| .map(|v| v.map(String::as_str).collect()), | ||
| }) | ||
| } | ||
| } | ||
|
|
@@ -264,12 +264,36 @@ fn stty(opts: &Options) -> UResult<()> { | |
| )); | ||
| } | ||
|
|
||
| // Fetch termios for the target device once and reuse it downstream | ||
| let base_termios = tcgetattr(opts.file.as_fd())?; | ||
|
|
||
| let mut set_arg = SetArg::TCSADRAIN; | ||
| let mut valid_args: Vec<ArgOptions> = Vec::new(); | ||
|
|
||
| // GNU compatible: If the first argument is a -g save format (hexadecimal colon-separated), | ||
| // restore termios from it before applying the remaining arguments. | ||
| let mut restored_from_save: Option<Termios> = None; | ||
| let mut settings_iter: Box<dyn Iterator<Item = &str>> = Box::new([].iter().copied()); | ||
|
|
||
| if let Some(args) = &opts.settings { | ||
| let mut args_iter = args.iter(); | ||
| while let Some(&arg) = args_iter.next() { | ||
| if let Some((first, rest)) = args.split_first() { | ||
| match parse_save_format(first, &base_termios) { | ||
| Ok(termios) => { | ||
| restored_from_save = Some(termios); | ||
| settings_iter = Box::new(rest.iter().copied()); | ||
| } | ||
| // GNU stty は先頭が save 形式っぽいのにパースできなければ即エラーにする | ||
|
||
| Err(e) if first.contains(':') => return Err(e), | ||
| Err(_) => { | ||
| settings_iter = Box::new(args.iter().map(|s| &**s)); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if restored_from_save.is_some() || opts.settings.is_some() { | ||
| let mut args_iter = settings_iter; | ||
| while let Some(arg) = args_iter.next() { | ||
| match arg { | ||
| "ispeed" | "ospeed" => match args_iter.next() { | ||
| Some(speed) => { | ||
|
|
@@ -396,15 +420,21 @@ fn stty(opts: &Options) -> UResult<()> { | |
| // combination setting | ||
| } else if let Some(combo) = string_to_combo(arg) { | ||
| valid_args.append(&mut combo_to_flags(combo)); | ||
| } else if arg.starts_with('-') { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mhlhl, this implies that doing
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right. Let's do that. |
||
| // For unknown options beginning with '-', treat them as invalid arguments. | ||
| return invalid_arg(arg); | ||
| } else { | ||
| // For bare words that are not recognized as flags, baud rates, | ||
| // control chars, or combinations, treat them as invalid arguments | ||
| // to match project test expectations. | ||
| return invalid_arg(arg); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // TODO: Figure out the right error message for when tcgetattr fails | ||
| let mut termios = tcgetattr(opts.file.as_fd())?; | ||
| let mut termios = restored_from_save.unwrap_or(base_termios); | ||
|
|
||
| // iterate over valid_args, match on the arg type, do the matching apply function | ||
| for arg in &valid_args { | ||
|
|
@@ -421,35 +451,27 @@ fn stty(opts: &Options) -> UResult<()> { | |
| } | ||
| tcsetattr(opts.file.as_fd(), set_arg, &termios)?; | ||
| } else { | ||
| // TODO: Figure out the right error message for when tcgetattr fails | ||
| let termios = tcgetattr(opts.file.as_fd())?; | ||
| print_settings(&termios, opts)?; | ||
| print_settings(&base_termios, opts)?; | ||
| } | ||
| Ok(()) | ||
| } | ||
|
|
||
| fn missing_arg<T>(arg: &str) -> Result<T, Box<dyn UError>> { | ||
| Err::<T, Box<dyn UError>>(USimpleError::new( | ||
| Err(USimpleError::new( | ||
| 1, | ||
| translate!( | ||
| "stty-error-missing-argument", | ||
| "arg" => *arg | ||
| ), | ||
| translate!("stty-error-missing-argument", "arg" => arg.to_string()), | ||
| )) | ||
| } | ||
|
|
||
| fn invalid_arg<T>(arg: &str) -> Result<T, Box<dyn UError>> { | ||
| Err::<T, Box<dyn UError>>(USimpleError::new( | ||
| Err(USimpleError::new( | ||
| 1, | ||
| translate!( | ||
| "stty-error-invalid-argument", | ||
| "arg" => *arg | ||
| ), | ||
| translate!("stty-error-invalid-argument", "arg" => arg.to_string()), | ||
| )) | ||
| } | ||
|
|
||
| fn invalid_integer_arg<T>(arg: &str) -> Result<T, Box<dyn UError>> { | ||
| Err::<T, Box<dyn UError>>(USimpleError::new( | ||
| Err(USimpleError::new( | ||
| 1, | ||
| translate!( | ||
| "stty-error-invalid-integer-argument", | ||
|
|
@@ -697,6 +719,63 @@ fn print_in_save_format(termios: &Termios) { | |
| println!(); | ||
| } | ||
|
|
||
| /// GNU stty -g compatibility: restore Termios from the colon-separated hexadecimal representation | ||
| /// produced by print_in_save_format. | ||
| fn parse_save_format(s: &str, base: &Termios) -> Result<Termios, Box<dyn UError>> { | ||
|
||
| // Expect four flag values + a variable-length sequence of cc_t values (at least one). | ||
| let parts: Vec<&str> = s.split(':').collect(); | ||
| if parts.len() < 5 { | ||
| return Err(USimpleError::new( | ||
| 1, | ||
| translate!("stty-error-invalid-argument", "arg" => s.to_string()), | ||
| )); | ||
| } | ||
|
|
||
| // Parse a hex string into tcflag_t (shared helper) to match the underlying bitflag type. | ||
| fn parse_hex_tcflag(x: &str, original: &str) -> Result<nix::libc::tcflag_t, Box<dyn UError>> { | ||
| nix::libc::tcflag_t::from_str_radix(x, 16).map_err(|_| { | ||
| USimpleError::new( | ||
| 1, | ||
| translate!("stty-error-invalid-argument", "arg" => original.to_string()), | ||
| ) | ||
| }) | ||
| } | ||
|
|
||
| let iflags_bits = parse_hex_tcflag(parts[0], s)?; | ||
| let oflags_bits = parse_hex_tcflag(parts[1], s)?; | ||
| let cflags_bits = parse_hex_tcflag(parts[2], s)?; | ||
| let lflags_bits = parse_hex_tcflag(parts[3], s)?; | ||
|
|
||
| // Remaining segments are control_chars. | ||
| let cc_hex = &parts[4..]; | ||
|
|
||
| // base で渡されたデバイスの termios をコピーし、プラットフォーム依存フィールドを保持する | ||
| let mut termios = base.clone(); | ||
|
|
||
| termios.input_flags = InputFlags::from_bits_truncate(iflags_bits); | ||
| termios.output_flags = OutputFlags::from_bits_truncate(oflags_bits); | ||
| termios.control_flags = ControlFlags::from_bits_truncate(cflags_bits); | ||
| termios.local_flags = LocalFlags::from_bits_truncate(lflags_bits); | ||
|
|
||
| // The length of control_chars depends on the runtime environment; fill only up to the | ||
| // length of the local array. | ||
| let max_cc = termios.control_chars.len(); | ||
| for (i, &hex) in cc_hex.iter().enumerate() { | ||
| if i >= max_cc { | ||
| break; | ||
| } | ||
| let val = u32::from_str_radix(hex, 16).map_err(|_| { | ||
| USimpleError::new( | ||
| 1, | ||
| translate!("stty-error-invalid-argument", "arg" => s.to_string()), | ||
| ) | ||
| })?; | ||
| termios.control_chars[i] = (val & 0xff) as u8; | ||
| } | ||
|
|
||
| Ok(termios) | ||
| } | ||
|
|
||
| fn print_settings(termios: &Termios, opts: &Options) -> nix::Result<()> { | ||
| if opts.save { | ||
| print_in_save_format(termios); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMHO this can be still just an Option set to None here, and you set it when needed, so before parsing, or - if unset - before applying the arguments.