diff --git a/src/uu/stty/src/stty.rs b/src/uu/stty/src/stty.rs index fdeee252df3..e4855798df5 100644 --- a/src/uu/stty/src/stty.rs +++ b/src/uu/stty/src/stty.rs @@ -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::(options::SETTINGS) - .map(|v| v.map(|s| s.as_ref()).collect()), + .map(|v| v.map(String::as_str).collect()), }) } } @@ -264,12 +264,47 @@ fn stty(opts: &Options) -> UResult<()> { )); } + // termios を最初に必要になるまで遅延取得し、1 度だけキャッシュする + let mut base_termios: Option = None; + let mut get_base_termios = |fd: BorrowedFd<'_>| -> nix::Result { + if let Some(ref termios) = base_termios { + Ok(termios.clone()) + } else { + let termios = tcgetattr(fd)?; + base_termios = Some(termios.clone()); + Ok(termios) + } + }; + let mut set_arg = SetArg::TCSADRAIN; let mut valid_args: Vec = 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 = None; + let mut settings_iter: Box> = 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() { + let base = get_base_termios(opts.file.as_fd())?; + let mut restored = base.clone(); + match parse_save_format(first, &mut restored) { + Ok(()) => { + restored_from_save = Some(restored); + settings_iter = Box::new(rest.iter().copied()); + } + // GNU stty errors immediately when the first argument looks like save format but cannot be parsed + 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 +431,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('-') { + // 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 base = get_base_termios(opts.file.as_fd())?; + let mut termios = restored_from_save.unwrap_or(base); // iterate over valid_args, match on the arg type, do the matching apply function for arg in &valid_args { @@ -421,35 +462,28 @@ 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)?; + let base = get_base_termios(opts.file.as_fd())?; + print_settings(&base, opts)?; } Ok(()) } fn missing_arg(arg: &str) -> Result> { - Err::>(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(arg: &str) -> Result> { - Err::>(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(arg: &str) -> Result> { - Err::>(USimpleError::new( + Err(USimpleError::new( 1, translate!( "stty-error-invalid-integer-argument", @@ -697,6 +731,60 @@ 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. Caller clones before passing and this function overwrites it. +fn parse_save_format(s: &str, termios: &mut Termios) -> Result<(), Box> { + // 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::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..]; + + 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(()) +} + fn print_settings(termios: &Termios, opts: &Options) -> nix::Result<()> { if opts.save { print_in_save_format(termios);