diff --git a/src/uu/stty/src/flags.rs b/src/uu/stty/src/flags.rs index d25936628d2..ea2b5b6c1b9 100644 --- a/src/uu/stty/src/flags.rs +++ b/src/uu/stty/src/flags.rs @@ -7,9 +7,10 @@ // 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 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] = &[ @@ -71,12 +76,21 @@ 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("tandem", I::IXOFF), Flag::new("ixon", I::IXON), - // not supported by nix - // Flag::new("iuclc", I::IUCLC), - #[cfg(not(target_os = "redox"))] + Flag::new("ixoff", I::IXOFF), + Flag::new("tandem", I::IXOFF).hidden(), + #[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), @@ -348,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. @@ -356,6 +405,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/src/uu/stty/src/stty.rs b/src/uu/stty/src/stty.rs index dc1019ce1ab..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)? }; @@ -1221,6 +1232,28 @@ 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", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "illumos", + ))] + (S::VSTATUS, "^T"), ]; } "tabs" => { @@ -1262,6 +1295,29 @@ 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", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "illumos", + ))] + S::VSTATUS => 20, // ^T _ => 0, } } @@ -1401,6 +1457,36 @@ 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 = "aix", + target_os = "illumos", + target_os = "solaris" + ))] + assert!(has_mapping(S::VDSUSP, 25)); // ^Y + #[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 +1681,36 @@ 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", + 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() { diff --git a/tests/by-util/test_stty.rs b/tests/by-util/test_stty.rs index 157758db903..69d35c84cad 100644 --- a/tests/by-util/test_stty.rs +++ b/tests/by-util/test_stty.rs @@ -44,6 +44,65 @@ 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 = "aix", + target_os = "illumos", + target_os = "solaris" + ))] + result.stdout_contains("dsusp"); + #[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] @@ -320,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"])