Skip to content

Conversation

@mattsu2020
Copy link
Contributor

Summary:

  • Improve option parsing and error handling in the stty subcommand.
  • Align behavior and messages more closely with GNU coreutils stty.
  • Refactor duplicated logic and add tests to improve readability and maintainability.

Details:

  • Adjusted error handling for invalid/unsupported options to better match GNU’s exit codes and error messages.
  • Refactored branching logic for combined options to prevent unintended behavior in edge cases.
  • Clarified responsibilities of internal helper functions and added comments to better document their behavior.
  • Added regression tests covering representative TTY configuration patterns to prevent future behavioral drift.

Rationale:

  • There were behavioral differences between this implementation and GNU coreutils that could cause unexpected results in existing scripts or tooling relying on GNU-compatible stty behavior.
  • This change reduces migration friction and makes stty usage more predictable across environments.

Scope / Impact:

  • The change is scoped to the stty utility only; no other utilities are affected.
  • The primary goal is compatibility and robustness; no breaking changes are intended for valid existing usages.

Testing:

  • cargo test --package uutils-stty --all-features
  • Added and executed new tests for relevant option combinations and error cases.
  • Performed manual verification for common TTY settings, display, and error scenarios.

#9056

- Add GNU-compatible parsing of colon-separated hex -g output
- Restore termios from saved state before applying new settings
- Preserve platform-dependent fields while validating input robustly
- Replace Box<dyn UError> with USimpleError for stty helpers and save-format parsing to reduce indirection and simplify error handling
- Use as_str/copied for argument iteration and convert flag bits via into() for clearer, more idiomatic Rust and improved type safety
- Update missing/invalid argument helpers to return Box<dyn UError> for consistent error typing
- Propagate errors with map_err(Into::into) at call sites to match new helper signatures
- Simplify string handling (String::as_str, &**s) to reduce verbosity and clarify intent
- Return USimpleError directly from missing_arg/invalid_* helpers to avoid Box indirection
- Replace redundant map_err(Into::into) uses with direct missing_arg calls for clarity
- Aligns error handling with UResult expectations and improves readability
- Fix mis-indented else branch to ensure missing_arg is called correctly
- Improve readability and maintain consistent control flow structure
- Cast parsed hex flag values to u64 to match bitflag expectations
- Remove redundant conversions when constructing termios flag sets
- Ensures proper handling of saved formats on platforms with 64-bit flags
- Use parse_hex_u32 return type directly for flag bitfields
- Simplifies code by eliminating unnecessary u32-to-u64 conversions
- Cast parsed flag bit values into appropriate integer types
- Prevent potential type mismatch or incorrect flag interpretation during restore
- Use tcflag_t-based parser for hex flag values to match termios types
- Avoid extra casts when reconstructing Input/Output/Control/Local flags for correctness and clarity
- Add tcflag, bitflag, and lflags to spell-checker ignore list
- Prevent false positives when working with recently added flag names
- Add "cflags" to spell-checker ignore hints in stty.rs
- Prevent false positives during spell-checking for terminal flag names
- Treat unknown dash-prefixed arguments as invalid options to match GNU stty behavior
- Treat unrecognized bare words as missing arguments for clearer error reporting
- Treat unrecognized options starting with '-' as invalid arguments
- Treat unrecognized bare words as invalid arguments instead of missing
- Match project test expectations and clarify the inline documentation
@github-actions
Copy link

GNU testsuite comparison:

Skip an intermittent issue tests/timeout/timeout (fails in this run but passes in the 'main' branch)
Skipping an intermittent issue tests/misc/tee (passes in this run but fails in the 'main' branch)

@codspeed-hq
Copy link

codspeed-hq bot commented Nov 13, 2025

CodSpeed Performance Report

Merging #9255 will not alter performance

Comparing mattsu2020:stty_fix (5d30e89) with main (2a314c7)

Summary

✅ 126 untouched
⏩ 6 skipped1

Footnotes

  1. 6 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@github-actions
Copy link

GNU testsuite comparison:

Skipping an intermittent issue tests/misc/tee (passes in this run but fails in the 'main' branch)


// Obtain the original termios and overwrite specific fields on top of it to preserve
// platform-dependent fields.
let mut termios = tcgetattr(std::io::stdout().as_fd())
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is is assuming that we're opening stdout, but no, it should be the device: tcgetattr(opts.file.as_fd()).

So, I think that this function should be instead moved at later point after all the args have been parsed, and it should take a &Termios, not returning it once it has been opened in the same way it's done currently

// combination setting
} else if let Some(combo) = string_to_combo(arg) {
valid_args.append(&mut combo_to_flags(combo));
} else if arg.starts_with('-') {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mhlhl, this implies that doing stty foo bar would pass, until we fail parsing... While to me we should just fail as GNU does in that case with stty: invalid argument 'foo'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. Let's do that.

Reuse base termios struct from target device to avoid redundant tcgetattr calls, improve performance, and ensure correctness in parse_save_format by cloning the base instead of hardcoding stdout. Also add GNU stty-compatible error handling for invalid save format with colons.
restored_from_save = Some(termios);
settings_iter = Box::new(rest.iter().copied());
}
// GNU stty は先頭が save 形式っぽいのにパースできなければ即エラーにする
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

???

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry fix

- Updated error handling comment to describe GNU stty behavior more clearly for English readers.
- Translated comment on termios cloning to explain preservation of platform-specific fields.
@github-actions
Copy link

GNU testsuite comparison:

Congrats! The gnu test tests/fold/fold is no longer failing!

}

// Fetch termios for the target device once and reuse it downstream
let base_termios = tcgetattr(opts.file.as_fd())?;
Copy link

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.


/// 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>> {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you just clone before passing and avoid returning it?

…rmance

- Delay the initial tcgetattr call until termios is actually needed, using a closure that caches the result.
- Modify parse_save_format to mutate the passed termios in-place instead of returning a clone, reducing unnecessary copies and improving efficiency.
@github-actions
Copy link

GNU testsuite comparison:

Congrats! The gnu test tests/fold/fold is no longer failing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants