From 4edffacd0c731e2577f1ec4a612d55857793172e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 16 Oct 2026 17:06:20 +0000 Subject: [PATCH 1/7] stty: Move solarish and dsusp to the cspell wordlist Rather than repeating file-level `spell-checker:ignore` comments, register the stty-specific words in the shared workspace dictionary so that they are covered everywhere, as suggested in review. --- .vscode/cspell.dictionaries/workspace.wordlist.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.vscode/cspell.dictionaries/workspace.wordlist.txt b/.vscode/cspell.dictionaries/workspace.wordlist.txt index 971e99afcdb..a2d6759417b 100644 --- a/.vscode/cspell.dictionaries/workspace.wordlist.txt +++ b/.vscode/cspell.dictionaries/workspace.wordlist.txt @@ -372,6 +372,7 @@ UCRT brkint cstopb decctlq +dsusp echoctl echoe echoke @@ -383,6 +384,7 @@ istrip litout opost parodd +solarish ENOTTY # translation tests From f679049beccedfef85007996450287c99ea5f877 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 16 Oct 2026 15:51:03 +0000 Subject: [PATCH 2/7] stty: Gate termios flags not defined on all platforms nix cfg-gates some termios flags and control characters out on certain platforms, but stty referenced them unconditionally, so the crate could not be built there at all: Haiku misses VREPRINT, VLNEXT, VWERASE, VDISCARD, IMAXBEL, EXTPROC, B460800 and B921600, and AIX misses VDISCARD, VWERASE, EXTPROC, CRTSCTS and the same high baud rates. Gate the affected definitions with the same predicates nix uses, so that the crate compiles on every target where its dependencies do, as prerequisite for the Haiku support that follows. --- src/uu/stty/src/flags.rs | 23 +++++++++++++++----- src/uu/stty/src/stty.rs | 45 +++++++++++++++++++++++++++------------- 2 files changed, 49 insertions(+), 19 deletions(-) diff --git a/src/uu/stty/src/flags.rs b/src/uu/stty/src/flags.rs index d25936628d2..2b1a3790bbc 100644 --- a/src/uu/stty/src/flags.rs +++ b/src/uu/stty/src/flags.rs @@ -57,7 +57,7 @@ pub const CONTROL_FLAGS: &[Flag] = &[ Flag::new("cstopb", C::CSTOPB), Flag::new("cread", C::CREAD).sane(), Flag::new("clocal", C::CLOCAL), - #[cfg(not(target_os = "redox"))] + #[cfg(not(any(target_os = "redox", target_os = "aix")))] Flag::new("crtscts", C::CRTSCTS), ]; @@ -78,7 +78,7 @@ pub const INPUT_FLAGS: &[Flag] = &[ // Flag::new("iuclc", I::IUCLC), #[cfg(not(target_os = "redox"))] Flag::new("ixany", I::IXANY), - #[cfg(not(target_os = "redox"))] + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] Flag::new("imaxbel", I::IMAXBEL).sane(), #[cfg(any(target_vendor = "apple", target_os = "android", target_os = "linux"))] Flag::new("iutf8", I::IUTF8), @@ -245,7 +245,12 @@ pub const LOCAL_FLAGS: &[Flag] = &[ Flag::new("crtkill", L::ECHOKE).sane().hidden(), #[cfg(not(target_os = "redox"))] Flag::new("flusho", L::FLUSHO), - #[cfg(not(any(target_os = "cygwin", target_os = "redox")))] + #[cfg(not(any( + target_os = "cygwin", + target_os = "redox", + target_os = "aix", + target_os = "haiku" + )))] Flag::new("extproc", L::EXTPROC), ]; @@ -276,7 +281,9 @@ pub const BAUD_RATES: &[(&str, BaudRate)] = &[ target_os = "cygwin", target_os = "fuchsia", target_os = "hurd", - target_os = "redox" + target_os = "redox", + target_os = "aix", + target_os = "haiku" )))] ("460800", BaudRate::B460800), #[cfg(any(target_os = "android", target_os = "linux"))] @@ -288,7 +295,9 @@ pub const BAUD_RATES: &[(&str, BaudRate)] = &[ target_os = "cygwin", target_os = "fuchsia", target_os = "hurd", - target_os = "redox" + target_os = "redox", + target_os = "aix", + target_os = "haiku" )))] ("921600", BaudRate::B921600), #[cfg(any(target_os = "android", target_os = "linux"))] @@ -348,12 +357,16 @@ pub const CONTROL_CHARS: &[(&str, S)] = &[ ("stop", S::VSTOP), // Sends a suspend signal (SIGTSTP). ("susp", S::VSUSP), + #[cfg(not(target_os = "haiku"))] // Reprints the current line. ("rprnt", S::VREPRINT), + #[cfg(not(any(target_os = "aix", target_os = "haiku")))] // Deletes the last word typed. ("werase", S::VWERASE), + #[cfg(not(target_os = "haiku"))] // Enters literal mode (next character is taken literally). ("lnext", S::VLNEXT), + #[cfg(not(any(target_os = "aix", target_os = "haiku")))] // Discards the current line. ("discard", S::VDISCARD), ]; diff --git a/src/uu/stty/src/stty.rs b/src/uu/stty/src/stty.rs index f4d71ae857a..35509eaae55 100644 --- a/src/uu/stty/src/stty.rs +++ b/src/uu/stty/src/stty.rs @@ -45,18 +45,22 @@ use flags::{CONTROL_CHARS, CONTROL_FLAGS, INPUT_FLAGS, LOCAL_FLAGS, OUTPUT_FLAGS const ASCII_DEL: u8 = 127; // Sane defaults for control characters. -const SANE_CONTROL_CHARS: [(S, u8); 12] = [ - (S::VINTR, 3), // ^C - (S::VQUIT, 28), // ^\ - (S::VERASE, 127), // DEL - (S::VKILL, 21), // ^U - (S::VEOF, 4), // ^D - (S::VSTART, 17), // ^Q - (S::VSTOP, 19), // ^S - (S::VSUSP, 26), // ^Z +const SANE_CONTROL_CHARS: &[(S, u8)] = &[ + (S::VINTR, 3), // ^C + (S::VQUIT, 28), // ^\ + (S::VERASE, 127), // DEL + (S::VKILL, 21), // ^U + (S::VEOF, 4), // ^D + (S::VSTART, 17), // ^Q + (S::VSTOP, 19), // ^S + (S::VSUSP, 26), // ^Z + #[cfg(not(target_os = "haiku"))] (S::VREPRINT, 18), // ^R - (S::VWERASE, 23), // ^W - (S::VLNEXT, 22), // ^V + #[cfg(not(any(target_os = "aix", target_os = "haiku")))] + (S::VWERASE, 23), // ^W + #[cfg(not(target_os = "haiku"))] + (S::VLNEXT, 22), // ^V + #[cfg(not(any(target_os = "aix", target_os = "haiku")))] (S::VDISCARD, 15), // ^O ]; @@ -1049,9 +1053,13 @@ fn combo_to_flags(combo: &str) -> Vec> { (S::VSTART, "^Q"), (S::VSTOP, "^S"), (S::VSUSP, "^Z"), + #[cfg(not(target_os = "haiku"))] (S::VREPRINT, "^R"), + #[cfg(not(any(target_os = "aix", target_os = "haiku")))] (S::VWERASE, "^W"), + #[cfg(not(target_os = "haiku"))] (S::VLNEXT, "^V"), + #[cfg(not(any(target_os = "aix", target_os = "haiku")))] (S::VDISCARD, "^O"), ], _ => &[], @@ -1109,7 +1117,7 @@ fn combo_to_flags(combo: &str) -> Vec> { } fn get_sane_control_char(cc_index: S) -> u8 { - for (sane_index, sane_val) in SANE_CONTROL_CHARS { + for &(sane_index, sane_val) in SANE_CONTROL_CHARS { if sane_index == cc_index { return sane_val; } @@ -1121,8 +1129,9 @@ fn get_sane_control_char(cc_index: S) -> u8 { S::VMIN => 1, #[cfg(not(any( all(target_os = "linux", target_arch = "sparc64"), - target_os = "illumos", - target_os = "solaris" + solarish, + target_os = "aix", + target_os = "haiku" )))] S::VTIME => 0, #[cfg(target_os = "linux")] @@ -1421,9 +1430,13 @@ mod tests { assert_eq!(cc_to_index("start"), Some(S::VSTART)); assert_eq!(cc_to_index("stop"), Some(S::VSTOP)); assert_eq!(cc_to_index("susp"), Some(S::VSUSP)); + #[cfg(not(target_os = "haiku"))] assert_eq!(cc_to_index("rprnt"), Some(S::VREPRINT)); + #[cfg(not(any(target_os = "aix", target_os = "haiku")))] assert_eq!(cc_to_index("werase"), Some(S::VWERASE)); + #[cfg(not(target_os = "haiku"))] assert_eq!(cc_to_index("lnext"), Some(S::VLNEXT)); + #[cfg(not(any(target_os = "aix", target_os = "haiku")))] assert_eq!(cc_to_index("discard"), Some(S::VDISCARD)); } @@ -1454,9 +1467,13 @@ mod tests { assert_eq!(get_sane_control_char(S::VSTART), 17); // ^Q assert_eq!(get_sane_control_char(S::VSTOP), 19); // ^S assert_eq!(get_sane_control_char(S::VSUSP), 26); // ^Z + #[cfg(not(target_os = "haiku"))] assert_eq!(get_sane_control_char(S::VREPRINT), 18); // ^R + #[cfg(not(any(target_os = "aix", target_os = "haiku")))] assert_eq!(get_sane_control_char(S::VWERASE), 23); // ^W + #[cfg(not(target_os = "haiku"))] assert_eq!(get_sane_control_char(S::VLNEXT), 22); // ^V + #[cfg(not(any(target_os = "aix", target_os = "haiku")))] assert_eq!(get_sane_control_char(S::VDISCARD), 15); // ^O } From ca333be420565a18975e0b116998e10311c04a7b 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 3/7] 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 2b1a3790bbc..464d4820f5e 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 c77f85ae75784895f63d7c03b3943236a4739888 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 4/7] 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 464d4820f5e..767bc293a0b 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 8c19779025fc00b355e4066d8ca8a16d136717bc 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 5/7] stty/flags: List all missing flags that GNU version supports Enable the remaining GNU flags that nix exposes as well (iuclc, ofill, ofdel and xcase), and list as comments the ones that are not supported by nix and libc (hup, cdtrdsr, flush) or that are handled manually (min and time), so that the lists stay in sync with GNU and it's easier to enable what will be supported in the future. --- src/uu/stty/build.rs | 3 ++ src/uu/stty/src/flags.rs | 55 ++++++++++++++++++-- src/uu/stty/src/stty.rs | 33 ++++++++++++ tests/by-util/test_stty.rs | 103 +++++++++++++++++++++++++++++++++++++ 4 files changed, 189 insertions(+), 5 deletions(-) diff --git a/src/uu/stty/build.rs b/src/uu/stty/build.rs index 320a109d007..0c5b94c10cf 100644 --- a/src/uu/stty/build.rs +++ b/src/uu/stty/build.rs @@ -3,6 +3,8 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +// spell-checker:ignore solarish + use cfg_aliases::cfg_aliases; fn main() { @@ -14,5 +16,6 @@ fn main() { target_os = "netbsd", target_os = "openbsd" ) }, + solarish: { any(target_os = "illumos", target_os = "solaris") }, }; } diff --git a/src/uu/stty/src/flags.rs b/src/uu/stty/src/flags.rs index 767bc293a0b..d2bc2fdc423 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), + // GNU's `hup`, hidden as GNU never displays it (OMIT). + Flag::new("hup", C::HUPCL).hidden(), Flag::new("cstopb", C::CSTOPB), Flag::new("cread", C::CREAD).sane(), Flag::new("clocal", C::CLOCAL), #[cfg(not(any(target_os = "redox", target_os = "aix")))] Flag::new("crtscts", C::CRTSCTS), + // Not supported by nix and libc. + // Flag::new("cdtrdsr", C::CDTRDSR), ]; pub const INPUT_FLAGS: &[Flag] = &[ @@ -74,9 +79,17 @@ 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 = "linux", + target_os = "nto", + solarish + ))] + Flag::new("iuclc", I::IUCLC), Flag::new("ixany", I::IXANY), #[cfg(not(any(target_os = "redox", target_os = "haiku")))] Flag::new("imaxbel", I::IMAXBEL).sane(), @@ -97,6 +110,20 @@ pub const OUTPUT_FLAGS: &[Flag] = &[ Flag::new("onlcr", O::ONLCR).sane(), Flag::new("onocr", O::ONOCR), 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 = "hurd", + target_os = "linux", + target_os = "nto", + target_os = "redox", + solarish + ))] + Flag::new("ofill", O::OFILL), #[cfg(any( target_vendor = "apple", target_os = "android", @@ -228,8 +255,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), @@ -369,6 +402,18 @@ pub const CONTROL_CHARS: &[(&str, S)] = &[ #[cfg(not(any(target_os = "aix", target_os = "haiku")))] // Discards the current line. ("discard", S::VDISCARD), + // deprecated compat option. + // Not supported by nix and libc. + // ("flush", S::VFLUSHO), + #[cfg(any(bsd, 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/src/uu/stty/src/stty.rs b/src/uu/stty/src/stty.rs index 35509eaae55..d9d9b1ca4bc 100644 --- a/src/uu/stty/src/stty.rs +++ b/src/uu/stty/src/stty.rs @@ -1061,6 +1061,10 @@ fn combo_to_flags(combo: &str) -> Vec> { (S::VLNEXT, "^V"), #[cfg(not(any(target_os = "aix", target_os = "haiku")))] (S::VDISCARD, "^O"), + #[cfg(any(bsd, solarish, target_os = "aix"))] + (S::VDSUSP, "^Y"), + #[cfg(any(bsd, target_os = "illumos"))] + (S::VSTATUS, "^T"), ], _ => &[], }; @@ -1136,6 +1140,11 @@ 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(bsd, solarish, target_os = "aix"))] + S::VDSUSP => 25, // ^Y + #[cfg(any(bsd, target_os = "illumos"))] + S::VSTATUS => 20, // ^T _ => 0, } } @@ -1275,6 +1284,18 @@ 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(bsd, solarish, target_os = "aix"))] + assert!(has_mapping(S::VDSUSP, 25)); // ^Y + #[cfg(any(bsd, target_os = "illumos"))] + assert!(has_mapping(S::VSTATUS, 20)); // ^T } #[test] @@ -1477,6 +1498,18 @@ mod tests { assert_eq!(get_sane_control_char(S::VDISCARD), 15); // ^O } + #[cfg(any(bsd, solarish, target_os = "aix"))] + #[test] + fn test_get_sane_control_char_dsusp() { + assert_eq!(get_sane_control_char(S::VDSUSP), 25); // ^Y + } + + #[cfg(any(bsd, 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() { diff --git a/tests/by-util/test_stty.rs b/tests/by-util/test_stty.rs index 157758db903..8bbc6b4d4c0 100644 --- a/tests/by-util/test_stty.rs +++ b/tests/by-util/test_stty.rs @@ -39,11 +39,114 @@ fn test_basic() { #[test] fn test_all_flag() { let (path, _controller, _replica) = pty_path(); + // The pty defaults for `status`/`dsusp` differ per platform (OpenBSD + // leaves `status` at \377), so set the sane values explicitly and check + // that they round-trip through the mapping. + #[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "illumos" + ))] + new_ucmd!() + .args(&["--file", &path, "status", "^T"]) + .succeeds(); let result = new_ucmd!().args(&["--all", "--file", &path]).succeeds(); for flag in ["parenb", "parmrk", "ixany", "onlcr", "icanon", "noflsh"] { result.stdout_contains(flag); } + + // Tokens are matched on the whitespace-split output, so the checks are + // stable against the line wrapping of `--all`. + let tokens: Vec<&str> = result.stdout_str().split_whitespace().collect(); + let position = |token: &str| tokens.iter().position(|&t| t == token); + let flag_position = |name: &str| { + tokens + .iter() + .position(|&t| t.trim_start_matches('-') == name) + }; + + // `tandem` is GNU's hidden alias of `ixoff` and is never printed. + assert!(!tokens.contains(&"tandem")); + + // GNU prints `ixon` before `ixoff`, whichever their state. + assert!(flag_position("ixon").unwrap() < flag_position("ixoff").unwrap()); + + // GNU-compatibility entries that are only defined on some platforms; they + // are off by default, so check their negated form (polarity). + #[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" + ))] + assert!(position("-iuclc").unwrap() > flag_position("ixoff").unwrap()); + #[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" + ))] + assert!(position("-ofill").is_some()); + #[cfg(any( + target_os = "aix", + target_os = "android", + target_os = "haiku", + target_os = "nto", + target_os = "linux", + ))] + assert!(position("-xcase").is_some()); + #[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "illumos", + ))] + { + // `status` resets to ^T on sane defaults; `--all` prints it as + // `status = ^T;`. + let status = position("status").unwrap(); + assert_eq!(tokens[status + 2], "^T;"); + } +} + +#[test] +fn test_hup_alias() { + // `hup` is GNU's hidden alias of `hupcl` and is never printed itself. + let (path, _controller, _replica) = pty_path(); + + new_ucmd!().args(&["--file", &path, "-hup"]).succeeds(); + new_ucmd!() + .args(&["--file", &path, "--all"]) + .succeeds() + .stdout_contains("-hupcl"); + + new_ucmd!().args(&["--file", &path, "hup"]).succeeds(); + new_ucmd!() + .args(&["--file", &path, "--all"]) + .succeeds() + .stdout_contains("hupcl") + .stdout_does_not_contain("-hupcl"); } #[test] From 288460c1433eafb514391cea91bfd026119b27e4 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 6/7] 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 | 5 ++++- src/uu/stty/src/stty.rs | 2 +- tests/by-util/test_stty.rs | 33 ++++++++++++++++++++++++++++++++- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/uu/stty/src/flags.rs b/src/uu/stty/src/flags.rs index d2bc2fdc423..b341812a419 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 @@ -390,6 +390,9 @@ pub const CONTROL_CHARS: &[(&str, S)] = &[ ("stop", S::VSTOP), // Sends a suspend signal (SIGTSTP). ("susp", S::VSUSP), + #[cfg(any(bsd, solarish, target_os = "aix"))] + // Sends a delayed suspend signal (SIGTSTP). + ("dsusp", S::VDSUSP), #[cfg(not(target_os = "haiku"))] // Reprints the current line. ("rprnt", S::VREPRINT), diff --git a/src/uu/stty/src/stty.rs b/src/uu/stty/src/stty.rs index d9d9b1ca4bc..8b808beded3 100644 --- a/src/uu/stty/src/stty.rs +++ b/src/uu/stty/src/stty.rs @@ -8,7 +8,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 ofill // 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 // spell-checker:ignore sigquit sigtstp // spell-checker:ignore cbreak decctlq evenp litout oddp tcsadrain exta extb NCCS cfsetispeed // spell-checker:ignore notaflag notacombo notabaud diff --git a/tests/by-util/test_stty.rs b/tests/by-util/test_stty.rs index 8bbc6b4d4c0..81918e8ebaf 100644 --- a/tests/by-util/test_stty.rs +++ b/tests/by-util/test_stty.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore parenb parmrk ixany iuclc onlcr ofdel icanon noflsh econl igpar ispeed ospeed NCCS nonhex gstty notachar cbreak evenp oddp CSIZE +// spell-checker:ignore parenb parmrk ixany iuclc onlcr ofdel icanon noflsh econl igpar ispeed ospeed NCCS nonhex gstty notachar cbreak dsusp evenp oddp CSIZE #![cfg(unix)] @@ -54,6 +54,20 @@ fn test_all_flag() { new_ucmd!() .args(&["--file", &path, "status", "^T"]) .succeeds(); + #[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" + ))] + new_ucmd!() + .args(&["--file", &path, "dsusp", "^Y"]) + .succeeds(); let result = new_ucmd!().args(&["--all", "--file", &path]).succeeds(); for flag in ["parenb", "parmrk", "ixany", "onlcr", "icanon", "noflsh"] { @@ -113,6 +127,23 @@ fn test_all_flag() { target_os = "linux", ))] assert!(position("-xcase").is_some()); + #[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" + ))] + { + // `dsusp` resets to ^Y on the sane defaults; `--all` prints it as + // `dsusp = ^Y;`. + let dsusp = position("dsusp").unwrap(); + assert_eq!(tokens[dsusp + 2], "^Y;"); + } #[cfg(any( target_os = "freebsd", target_os = "dragonfly", From 056caab5fd13f8a991cede07304130aefc738606 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 7/7] stty: Use nix line_discipline on linux/android/haiku Nix exposes the Termios line discipline field only on linux, android and haiku, so use it there and keep reading c_line through libc on redox, where nix does not provide the field. On Haiku the field is a c_char, so convert the value from u8 when setting it. See: https://github.com/nix-rust/nix/issues/1802 --- src/uu/stty/src/stty.rs | 25 ++++++++++++++++++++++--- tests/by-util/test_stty.rs | 2 +- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/uu/stty/src/stty.rs b/src/uu/stty/src/stty.rs index 8b808beded3..8b3ad0b9a2a 100644 --- a/src/uu/stty/src/stty.rs +++ b/src/uu/stty/src/stty.rs @@ -611,12 +611,25 @@ fn print_terminal_size( ); } - #[cfg(any(target_os = "linux", target_os = "redox"))] + #[cfg(any(target_os = "linux", target_os = "android", target_os = "haiku"))] { let line = termios.line_discipline; printer.print(&translate!("stty-output-line", "line" => line)); } - #[cfg(not(any(target_os = "linux", target_os = "redox")))] + #[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)); + } + #[cfg(not(any( + target_os = "linux", + target_os = "android", + target_os = "haiku", + target_os = "redox" + )))] let _ = termios; // remove this hack when we switched to rustix completely printer.flush(); Ok(()) @@ -968,7 +981,7 @@ fn apply_special_setting( SpecialSetting::Rows(n) => size.ws_row = *n, SpecialSetting::Cols(n) => size.ws_col = *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) => { @@ -977,6 +990,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)?; + } } } tcsetwinsize(fd, size)?; diff --git a/tests/by-util/test_stty.rs b/tests/by-util/test_stty.rs index 81918e8ebaf..1efb7685baa 100644 --- a/tests/by-util/test_stty.rs +++ b/tests/by-util/test_stty.rs @@ -454,7 +454,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"])