From 54d680b2b22dae8702d0455c97839dd9ae7a1b55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 21 Nov 2025 04:17:16 +0100 Subject: [PATCH 1/6] stty/flags: Do not show tandem option It is just an alias for ixoff, and it's marked with `OMIT` in GNU version --- src/uu/stty/src/flags.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/stty/src/flags.rs b/src/uu/stty/src/flags.rs index d25936628d2..98e525a7f1d 100644 --- a/src/uu/stty/src/flags.rs +++ b/src/uu/stty/src/flags.rs @@ -72,8 +72,8 @@ pub const INPUT_FLAGS: &[Flag] = &[ Flag::new("igncr", I::IGNCR), Flag::new("icrnl", I::ICRNL).sane(), Flag::new("ixoff", I::IXOFF), - Flag::new("tandem", I::IXOFF), Flag::new("ixon", I::IXON), + Flag::new("tandem", I::IXOFF).hidden(), // not supported by nix // Flag::new("iuclc", I::IUCLC), #[cfg(not(target_os = "redox"))] From 3f5e954b0178a3177b8763601419ff66727262e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 21 Nov 2025 04:19:22 +0100 Subject: [PATCH 2/6] stty/flags: Use same order of GNU in printing ixoff/ixon Now the order is respected --- src/uu/stty/src/flags.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/stty/src/flags.rs b/src/uu/stty/src/flags.rs index 98e525a7f1d..1bd107f6ca3 100644 --- a/src/uu/stty/src/flags.rs +++ b/src/uu/stty/src/flags.rs @@ -71,8 +71,8 @@ pub const INPUT_FLAGS: &[Flag] = &[ Flag::new("inlcr", I::INLCR), Flag::new("igncr", I::IGNCR), Flag::new("icrnl", I::ICRNL).sane(), - Flag::new("ixoff", I::IXOFF), Flag::new("ixon", I::IXON), + Flag::new("ixoff", I::IXOFF), Flag::new("tandem", I::IXOFF).hidden(), // not supported by nix // Flag::new("iuclc", I::IUCLC), From bab993e5cd3c301b610cd8db61847bcf4a80cbbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 21 Nov 2025 04:23:52 +0100 Subject: [PATCH 3/6] stty/flags: List all missing flags that GNU version supports List the remaining flags that GNU stty support, they cannot be be all listed yet since we have no nix support for them, but it's better to keep the lists in sync so that it's easier to enable what will be supported in future --- src/uu/stty/src/flags.rs | 68 ++++++++++++++++++++++++++++++++++---- tests/by-util/test_stty.rs | 47 ++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 6 deletions(-) diff --git a/src/uu/stty/src/flags.rs b/src/uu/stty/src/flags.rs index 1bd107f6ca3..89a527667bc 100644 --- a/src/uu/stty/src/flags.rs +++ b/src/uu/stty/src/flags.rs @@ -10,6 +10,7 @@ // 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 +// spell-checker:ignore cdtrdsr CDTRDSR ofill OFILL VFLUSHO VSTATUS noncanonical VMIN deciseconds noncanonical VTIME use crate::Flag; @@ -54,11 +55,15 @@ pub const CONTROL_FLAGS: &[Flag] = &[ Flag::new_grouped("cs7", C::CS7, C::CSIZE), Flag::new_grouped("cs8", C::CS8, C::CSIZE).sane(), Flag::new("hupcl", C::HUPCL), + // Not supported by nix and libc. + // Flag::new("hup", C::HUP).hidden(), Flag::new("cstopb", C::CSTOPB), Flag::new("cread", C::CREAD).sane(), Flag::new("clocal", C::CLOCAL), #[cfg(not(target_os = "redox"))] Flag::new("crtscts", C::CRTSCTS), + // Not supported by nix and libc. + // Flag::new("cdtrdsr", C::CDTRDSR), ]; pub const INPUT_FLAGS: &[Flag] = &[ @@ -74,9 +79,18 @@ pub const INPUT_FLAGS: &[Flag] = &[ Flag::new("ixon", I::IXON), Flag::new("ixoff", I::IXOFF), Flag::new("tandem", I::IXOFF).hidden(), - // not supported by nix - // Flag::new("iuclc", I::IUCLC), - #[cfg(not(target_os = "redox"))] + #[cfg(any( + target_os = "aix", + target_os = "android", + target_os = "cygwin", + target_os = "haiku", + target_os = "hurd", + target_os = "illumos", + target_os = "nto", + target_os = "solaris", + target_os = "linux" + ))] + Flag::new("iuclc", I::IUCLC), Flag::new("ixany", I::IXANY), #[cfg(not(target_os = "redox"))] Flag::new("imaxbel", I::IMAXBEL).sane(), @@ -99,9 +113,25 @@ pub const OUTPUT_FLAGS: &[Flag] = &[ Flag::new("onlret", O::ONLRET), #[cfg(any( target_vendor = "apple", + target_os = "aix", target_os = "android", + target_os = "cygwin", + target_os = "fuchsia", target_os = "haiku", - target_os = "linux" + target_os = "hurd", + target_os = "illumos", + target_os = "linux", + target_os = "nto", + target_os = "redox", + target_os = "solaris" + ))] + Flag::new("ofill", O::OFILL), + #[cfg(any( + target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos" ))] Flag::new("ofdel", O::OFDEL), #[cfg(any( @@ -228,8 +258,14 @@ pub const LOCAL_FLAGS: &[Flag] = &[ Flag::new("echok", L::ECHOK).sane(), Flag::new("echonl", L::ECHONL), Flag::new("noflsh", L::NOFLSH), - // Not supported by nix - // Flag::new("xcase", L::XCASE), + #[cfg(any( + target_os = "aix", + target_os = "android", + target_os = "haiku", + target_os = "nto", + target_os = "linux", + ))] + Flag::new("xcase", L::XCASE), Flag::new("tostop", L::TOSTOP), #[cfg(not(any(target_os = "cygwin", target_os = "redox")))] Flag::new("echoprt", L::ECHOPRT), @@ -356,6 +392,26 @@ pub const CONTROL_CHARS: &[(&str, S)] = &[ ("lnext", S::VLNEXT), // Discards the current line. ("discard", S::VDISCARD), + // deprecated compat option. + // Not supported by nix and libc. + // ("flush", S::VFLUSHO), + #[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "illumos", + ))] + // Status character + ("status", S::VSTATUS), + // Minimum number of characters for noncanonical read. + // We handle this manually. + // ("min", S::VMIN), + // Timeout in deciseconds for noncanonical read. + // We handle this manually. + // ("time", S::VTIME), ]; /// This constant lists all possible combination settings, using a bool to represent if the setting is negatable diff --git a/tests/by-util/test_stty.rs b/tests/by-util/test_stty.rs index 157758db903..a8d9a5eb658 100644 --- a/tests/by-util/test_stty.rs +++ b/tests/by-util/test_stty.rs @@ -44,6 +44,53 @@ fn test_all_flag() { for flag in ["parenb", "parmrk", "ixany", "onlcr", "icanon", "noflsh"] { result.stdout_contains(flag); } + + // GNU-compatibility entries that are only defined on some platforms. + #[cfg(any( + target_os = "aix", + target_os = "android", + target_os = "cygwin", + target_os = "haiku", + target_os = "hurd", + target_os = "illumos", + target_os = "nto", + target_os = "solaris", + target_os = "linux" + ))] + result.stdout_contains("iuclc"); + #[cfg(any( + target_vendor = "apple", + target_os = "aix", + target_os = "android", + target_os = "cygwin", + target_os = "fuchsia", + target_os = "haiku", + target_os = "hurd", + target_os = "illumos", + target_os = "linux", + target_os = "nto", + target_os = "redox", + target_os = "solaris" + ))] + result.stdout_contains("ofill"); + #[cfg(any( + target_os = "aix", + target_os = "android", + target_os = "haiku", + target_os = "nto", + target_os = "linux", + ))] + result.stdout_contains("xcase"); + #[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "illumos", + ))] + result.stdout_contains("status"); } #[test] From 875412075ff553db2ecbd8cbf02389da2c0abfe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 16 Sep 2026 04:24:09 +0200 Subject: [PATCH 4/6] stty: Add default value for status --- src/uu/stty/src/stty.rs | 52 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/uu/stty/src/stty.rs b/src/uu/stty/src/stty.rs index dc1019ce1ab..306bd21498c 100644 --- a/src/uu/stty/src/stty.rs +++ b/src/uu/stty/src/stty.rs @@ -1221,6 +1221,16 @@ fn combo_to_flags(combo: &str) -> Vec> { (S::VWERASE, "^W"), (S::VLNEXT, "^V"), (S::VDISCARD, "^O"), + #[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "illumos", + ))] + (S::VSTATUS, "^T"), ]; } "tabs" => { @@ -1262,6 +1272,16 @@ fn get_sane_control_char(cc_index: S) -> u8 { S::VTIME => 0, #[cfg(target_os = "linux")] S::VSWTC => 0, + #[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "illumos", + ))] + S::VSTATUS => 20, // ^T _ => 0, } } @@ -1401,6 +1421,24 @@ mod tests { fn test_combo_to_flags_sane() { let flags = combo_to_flags("sane"); assert!(flags.len() > 5); // sane sets multiple flags + + let has_mapping = |cc: S, val: u8| { + flags + .iter() + .any(|f| matches!(f, ArgOptions::Mapping((idx, v)) if *idx == cc && *v == val)) + }; + // sane always resets the standard control characters. + assert!(has_mapping(S::VINTR, 3)); // ^C + #[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "illumos", + ))] + assert!(has_mapping(S::VSTATUS, 20)); // ^T } #[test] @@ -1595,6 +1633,20 @@ mod tests { assert_eq!(get_sane_control_char(S::VDISCARD), 15); // ^O } + #[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "illumos", + ))] + #[test] + fn test_get_sane_control_char_status() { + assert_eq!(get_sane_control_char(S::VSTATUS), 20); // ^T + } + // Tests for parse_u8_or_err #[test] fn test_parse_u8_or_err_valid() { From 70af8dde0a364859c4f6b3f3561a06adbd290d1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 21 Nov 2025 04:22:13 +0100 Subject: [PATCH 5/6] stty/flags: Add delayed suspend (dsusp) support It's only supported by BSD, aix and solaris, but still available in nix. We need to define a sane default for VDSUSP (^Y) --- src/uu/stty/src/flags.rs | 15 ++++++++++- src/uu/stty/src/stty.rs | 53 ++++++++++++++++++++++++++++++++++++++ tests/by-util/test_stty.rs | 12 +++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/src/uu/stty/src/flags.rs b/src/uu/stty/src/flags.rs index 89a527667bc..ea2b5b6c1b9 100644 --- a/src/uu/stty/src/flags.rs +++ b/src/uu/stty/src/flags.rs @@ -7,7 +7,7 @@ // spell-checker:ignore ignbrk brkint ignpar parmrk inpck istrip inlcr igncr icrnl ixoff ixon iuclc ixany imaxbel iutf // spell-checker:ignore opost olcuc ocrnl onlcr onocr onlret ofdel nldly crdly tabdly bsdly vtdly ffdly // 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 lnext rprnt susp dsusp swtch vdiscard veof veol verase vintr vkill vlnext vquit vreprint vstart vstop vsusp vswtc vwerase werase VDSUSP // spell-checker:ignore sigquit sigtstp // spell-checker:ignore cbreak decctlq evenp litout oddp // spell-checker:ignore cdtrdsr CDTRDSR ofill OFILL VFLUSHO VSTATUS noncanonical VMIN deciseconds noncanonical VTIME @@ -384,6 +384,19 @@ pub const CONTROL_CHARS: &[(&str, S)] = &[ ("stop", S::VSTOP), // Sends a suspend signal (SIGTSTP). ("susp", S::VSUSP), + #[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "aix", + target_os = "illumos", + target_os = "solaris" + ))] + // Sends a delayed suspend signal (SIGTSTP). + ("dsusp", S::VDSUSP), // Reprints the current line. ("rprnt", S::VREPRINT), // Deletes the last word typed. diff --git a/src/uu/stty/src/stty.rs b/src/uu/stty/src/stty.rs index 306bd21498c..b08416e79de 100644 --- a/src/uu/stty/src/stty.rs +++ b/src/uu/stty/src/stty.rs @@ -1221,6 +1221,18 @@ fn combo_to_flags(combo: &str) -> Vec> { (S::VWERASE, "^W"), (S::VLNEXT, "^V"), (S::VDISCARD, "^O"), + #[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "aix", + target_os = "illumos", + target_os = "solaris" + ))] + (S::VDSUSP, "^Y"), #[cfg(any( target_os = "freebsd", target_os = "dragonfly", @@ -1272,6 +1284,19 @@ fn get_sane_control_char(cc_index: S) -> u8 { S::VTIME => 0, #[cfg(target_os = "linux")] S::VSWTC => 0, + // BSD-family sane defaults (GNU uses CDSUSP = ^Y, CSTATUS = ^T). + #[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "aix", + target_os = "illumos", + target_os = "solaris" + ))] + S::VDSUSP => 25, // ^Y #[cfg(any( target_os = "freebsd", target_os = "dragonfly", @@ -1429,6 +1454,18 @@ mod tests { }; // sane always resets the standard control characters. assert!(has_mapping(S::VINTR, 3)); // ^C + #[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "aix", + target_os = "illumos", + target_os = "solaris" + ))] + assert!(has_mapping(S::VDSUSP, 25)); // ^Y #[cfg(any( target_os = "freebsd", target_os = "dragonfly", @@ -1633,6 +1670,22 @@ mod tests { assert_eq!(get_sane_control_char(S::VDISCARD), 15); // ^O } + #[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "aix", + target_os = "illumos", + target_os = "solaris" + ))] + #[test] + fn test_get_sane_control_char_dsusp() { + assert_eq!(get_sane_control_char(S::VDSUSP), 25); // ^Y + } + #[cfg(any( target_os = "freebsd", target_os = "dragonfly", diff --git a/tests/by-util/test_stty.rs b/tests/by-util/test_stty.rs index a8d9a5eb658..8cb965eee20 100644 --- a/tests/by-util/test_stty.rs +++ b/tests/by-util/test_stty.rs @@ -81,6 +81,18 @@ fn test_all_flag() { target_os = "linux", ))] result.stdout_contains("xcase"); + #[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "aix", + target_os = "illumos", + target_os = "solaris" + ))] + result.stdout_contains("dsusp"); #[cfg(any( target_os = "freebsd", target_os = "dragonfly", From 989b486912baebee08ee056140ccb1a651f03f83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Sat, 22 Nov 2025 05:15:15 +0100 Subject: [PATCH 6/6] stty: Use c_line (as line_discipline) from nix Nix now supports the line discipline parameter in various platforms, so use upstream definition instead of going through libc See: https://github.com/nix-rust/nix/issues/1802 --- src/uu/stty/src/stty.rs | 19 +++++++++++++++---- tests/by-util/test_stty.rs | 2 +- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/uu/stty/src/stty.rs b/src/uu/stty/src/stty.rs index b08416e79de..727e680c90c 100644 --- a/src/uu/stty/src/stty.rs +++ b/src/uu/stty/src/stty.rs @@ -690,10 +690,15 @@ fn print_terminal_size( ); } - #[cfg(any(target_os = "linux", target_os = "redox"))] + #[cfg(any(target_os = "linux", target_os = "android", target_os = "haiku"))] { - // For some reason the normal nix Termios struct does not expose the line, - // so we get the underlying libc::termios struct to get that information. + let line = termios.line_discipline; + printer.print(&translate!("stty-output-line", "line" => line)); + } + #[cfg(target_os = "redox")] + { + // nix does not expose the line discipline on Redox, so we get the + // underlying libc::termios struct to read that information. let libc_termios: nix::libc::termios = termios.clone().into(); let line = libc_termios.c_line; printer.print(&translate!("stty-output-line", "line" => line)); @@ -1053,7 +1058,7 @@ fn apply_special_setting( SpecialSetting::Rows(n) => size.rows = *n, SpecialSetting::Cols(n) => size.columns = *n, #[cfg_attr( - not(any(target_os = "linux", target_os = "android")), + not(any(target_os = "linux", target_os = "android", target_os = "haiku")), expect(unused_variables) )] SpecialSetting::Line(n) => { @@ -1062,6 +1067,12 @@ fn apply_special_setting( { _termios.line_discipline = *n; } + // On Haiku the field is a `c_char`, so convert from `u8` first. + #[cfg(target_os = "haiku")] + { + _termios.line_discipline = + (*n).try_into().map_err(|_| nix::errno::Errno::ERANGE)?; + } } } unsafe { tiocswinsz(fd, &raw mut size)? }; diff --git a/tests/by-util/test_stty.rs b/tests/by-util/test_stty.rs index 8cb965eee20..69d35c84cad 100644 --- a/tests/by-util/test_stty.rs +++ b/tests/by-util/test_stty.rs @@ -379,7 +379,7 @@ fn test_row_column_hex_octal() { } #[test] -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(any(target_os = "linux", target_os = "android", target_os = "haiku"))] fn line() { new_ucmd!() .args(&["line"])