Skip to content

Commit e9a00c3

Browse files
committed
stty: support sane settings
1 parent 46cfead commit e9a00c3

File tree

2 files changed

+121
-8
lines changed

2 files changed

+121
-8
lines changed

src/uu/stty/src/stty.rs

Lines changed: 111 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// file that was distributed with this source code.
55

66
// spell-checker:ignore clocal erange tcgetattr tcsetattr tcsanow tiocgwinsz tiocswinsz cfgetospeed cfsetospeed ushort vmin vtime cflag lflag ispeed ospeed
7+
// spell-checker:ignore vintr vquit verase vkill veof vstart vstop vsusp vreprint vwerase vlnext vdiscard veol vswtc vswtc
78

89
mod flags;
910

@@ -38,6 +39,22 @@ use flags::{CONTROL_CHARS, CONTROL_FLAGS, INPUT_FLAGS, LOCAL_FLAGS, OUTPUT_FLAGS
3839

3940
const ASCII_DEL: u8 = 127;
4041

42+
// Sane defaults for control characters.
43+
const SANE_CONTROL_CHARS: [(SpecialCharacterIndices, u8); 12] = [
44+
(SpecialCharacterIndices::VINTR, 3), // ^C
45+
(SpecialCharacterIndices::VQUIT, 28), // ^\
46+
(SpecialCharacterIndices::VERASE, 127), // DEL
47+
(SpecialCharacterIndices::VKILL, 21), // ^U
48+
(SpecialCharacterIndices::VEOF, 4), // ^D
49+
(SpecialCharacterIndices::VSTART, 17), // ^Q
50+
(SpecialCharacterIndices::VSTOP, 19), // ^S
51+
(SpecialCharacterIndices::VSUSP, 26), // ^Z
52+
(SpecialCharacterIndices::VREPRINT, 18), // ^R
53+
(SpecialCharacterIndices::VWERASE, 23), // ^W
54+
(SpecialCharacterIndices::VLNEXT, 22), // ^V
55+
(SpecialCharacterIndices::VDISCARD, 15), // ^O
56+
];
57+
4158
#[derive(Clone, Copy, Debug)]
4259
pub struct Flag<T> {
4360
name: &'static str,
@@ -112,6 +129,7 @@ enum ControlCharMappingError {
112129
enum SpecialSetting {
113130
Rows(u16),
114131
Cols(u16),
132+
Sane,
115133
}
116134

117135
enum PrintSetting {
@@ -367,6 +385,8 @@ fn stty(opts: &Options) -> UResult<()> {
367385
}
368386
} else if *arg == "size" {
369387
valid_args.push(ArgOptions::Print(PrintSetting::Size));
388+
} else if *arg == "sane" {
389+
valid_args.push(ArgOptions::Special(SpecialSetting::Sane));
370390
// not a valid option
371391
} else {
372392
return Err(USimpleError::new(
@@ -388,7 +408,7 @@ fn stty(opts: &Options) -> UResult<()> {
388408
ArgOptions::Mapping(mapping) => apply_char_mapping(&mut termios, mapping),
389409
ArgOptions::Flags(flag) => apply_setting(&mut termios, flag),
390410
ArgOptions::Special(setting) => {
391-
apply_special_setting(setting, opts.file.as_raw_fd())?;
411+
apply_special_setting(setting, opts.file.as_raw_fd(), &mut termios)?;
392412
}
393413
ArgOptions::Print(setting) => {
394414
print_special_setting(setting, opts.file.as_raw_fd())?;
@@ -605,7 +625,21 @@ fn control_char_to_string(cc: nix::libc::cc_t) -> nix::Result<String> {
605625

606626
fn print_control_chars(termios: &Termios, opts: &Options) -> nix::Result<()> {
607627
if !opts.all {
608-
// TODO: this branch should print values that differ from defaults
628+
// Print only control chars that differ from sane defaults
629+
let mut printed = false;
630+
for (text, cc_index) in CONTROL_CHARS {
631+
let current_val = termios.control_chars[*cc_index as usize];
632+
let sane_val = get_sane_control_char(*cc_index);
633+
634+
if current_val != sane_val {
635+
print!("{text} = {}; ", control_char_to_string(current_val)?);
636+
printed = true;
637+
}
638+
}
639+
640+
if printed {
641+
println!();
642+
}
609643
return Ok(());
610644
}
611645

@@ -745,14 +779,28 @@ fn apply_char_mapping(termios: &mut Termios, mapping: &(SpecialCharacterIndices,
745779
termios.control_chars[mapping.0 as usize] = mapping.1;
746780
}
747781

748-
fn apply_special_setting(setting: &SpecialSetting, fd: i32) -> nix::Result<()> {
749-
let mut size = TermSize::default();
750-
unsafe { tiocgwinsz(fd, &raw mut size)? };
782+
fn apply_special_setting(
783+
setting: &SpecialSetting,
784+
fd: i32,
785+
termios: &mut Termios,
786+
) -> nix::Result<()> {
751787
match setting {
752-
SpecialSetting::Rows(n) => size.rows = *n,
753-
SpecialSetting::Cols(n) => size.columns = *n,
788+
SpecialSetting::Rows(n) => {
789+
let mut size = TermSize::default();
790+
unsafe { tiocgwinsz(fd, &raw mut size)? };
791+
size.rows = *n;
792+
unsafe { tiocswinsz(fd, &raw mut size)? };
793+
}
794+
SpecialSetting::Cols(n) => {
795+
let mut size = TermSize::default();
796+
unsafe { tiocgwinsz(fd, &raw mut size)? };
797+
size.columns = *n;
798+
unsafe { tiocswinsz(fd, &raw mut size)? };
799+
}
800+
SpecialSetting::Sane => {
801+
apply_sane_settings(termios);
802+
}
754803
}
755-
unsafe { tiocswinsz(fd, &raw mut size)? };
756804
Ok(())
757805
}
758806

@@ -807,6 +855,61 @@ fn string_to_control_char(s: &str) -> Result<u8, ControlCharMappingError> {
807855
}
808856
}
809857

858+
fn get_sane_control_char(cc_index: SpecialCharacterIndices) -> u8 {
859+
for (sane_index, sane_val) in SANE_CONTROL_CHARS {
860+
if sane_index == cc_index {
861+
return sane_val;
862+
}
863+
}
864+
// Default values for control chars not in the sane list
865+
match cc_index {
866+
SpecialCharacterIndices::VEOL => 0,
867+
SpecialCharacterIndices::VEOL2 => 0,
868+
SpecialCharacterIndices::VMIN => 1,
869+
SpecialCharacterIndices::VTIME => 0,
870+
#[cfg(target_os = "linux")]
871+
SpecialCharacterIndices::VSWTC => 0,
872+
_ => 0,
873+
}
874+
}
875+
876+
fn apply_sane_settings(termios: &mut Termios) {
877+
// Set sane control characters
878+
for (cc_index, sane_val) in SANE_CONTROL_CHARS {
879+
termios.control_chars[cc_index as usize] = sane_val;
880+
}
881+
882+
// Set other sane control character defaults
883+
termios.control_chars[SpecialCharacterIndices::VEOL as usize] = 0;
884+
termios.control_chars[SpecialCharacterIndices::VEOL2 as usize] = 0;
885+
termios.control_chars[SpecialCharacterIndices::VMIN as usize] = 1;
886+
termios.control_chars[SpecialCharacterIndices::VTIME as usize] = 0;
887+
#[cfg(target_os = "linux")]
888+
{
889+
termios.control_chars[SpecialCharacterIndices::VSWTC as usize] = 0;
890+
}
891+
892+
// Apply sane flags
893+
use flags::{CONTROL_FLAGS, INPUT_FLAGS, LOCAL_FLAGS, OUTPUT_FLAGS};
894+
895+
macro_rules! apply_sane_flags {
896+
($flags:ident) => {
897+
for flag in $flags {
898+
if flag.sane {
899+
flag.flag.apply(termios, true);
900+
} else {
901+
flag.flag.apply(termios, false);
902+
}
903+
}
904+
};
905+
}
906+
907+
apply_sane_flags!(CONTROL_FLAGS);
908+
apply_sane_flags!(INPUT_FLAGS);
909+
apply_sane_flags!(OUTPUT_FLAGS);
910+
apply_sane_flags!(LOCAL_FLAGS);
911+
}
912+
810913
pub fn uu_app() -> Command {
811914
Command::new(uucore::util_name())
812915
.version(uucore::crate_version!())

tests/by-util/test_stty.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,3 +243,13 @@ fn row_column_sizes() {
243243
.fails()
244244
.stderr_contains("missing argument to 'rows'");
245245
}
246+
247+
#[test]
248+
fn sane_settings() {
249+
new_ucmd!().args(&["intr", ""]).succeeds();
250+
new_ucmd!().succeeds().stdout_contains("intr = <undef>");
251+
new_ucmd!()
252+
.args(&["sane"])
253+
.succeeds()
254+
.stdout_str_check(|s| !s.contains("intr = <undef>"));
255+
}

0 commit comments

Comments
 (0)