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

@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 (178b7b2) with main (370ea74)

Summary

✅ 88 untouched
⏩ 45 skipped1

Footnotes

  1. 45 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.

@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?

@github-actions
Copy link

GNU testsuite comparison:

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

@ChrisDryden
Copy link
Collaborator

I think my latests CR's have a bit of conflicts with this and was wondering if you're still working on it

@github-actions
Copy link

github-actions bot commented Dec 2, 2025

GNU testsuite comparison:

Skip an intermittent issue tests/tail/overlay-headers (fails in this run but passes in the 'main' branch)

if i >= max_cc {
break;
}
let val = u32::from_str_radix(hex, 16).map_err(|_| {
Copy link

Choose a reason for hiding this comment

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

Is this another case of rust-lang/rust-clippy#16213 ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! That’s the same from_str_radix pitfall (leading +). We can add a hex-only check if we want to be strict

@github-actions
Copy link

GNU testsuite comparison:

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

- 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
- 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
…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.
Add a new test case `invalid_free_word_fails_immediately` to verify that `stty` appropriately handles and rejects invalid arguments like 'foo' and 'bar', ensuring the command fails with an error message "invalid argument 'foo'". This improves test coverage for argument validation.
- 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:

GNU test failed: tests/tty/tty-eof. tests/tty/tty-eof is passing on 'main'. Maybe you have to rebase?

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.

4 participants