Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/uu/stty/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ doctest = false

[dependencies]
clap = { workspace = true }
thiserror = { workspace = true }
uucore = { workspace = true, features = ["parser"] }
fluent = { workspace = true }

Expand Down
1 change: 1 addition & 0 deletions src/uu/stty/locales/en-US.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ stty-error-invalid-speed = invalid {$arg} '{$speed}'
stty-error-invalid-argument = invalid argument '{$arg}'
stty-error-invalid-integer-argument = invalid integer argument: {$value}
stty-error-invalid-integer-argument-value-too-large = invalid integer argument: {$value}: Value too large for defined data type
stty-error-write-error = write error

# Output format strings
stty-output-speed = speed {$speed} baud;
Expand Down
1 change: 1 addition & 0 deletions src/uu/stty/locales/fr-FR.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ stty-error-invalid-speed = {$arg} invalide '{$speed}'
stty-error-invalid-argument = argument invalide '{$arg}'
stty-error-invalid-integer-argument = argument entier invalide : {$value}
stty-error-invalid-integer-argument-value-too-large = argument entier invalide : {$value} : Valeur trop grande pour le type de données défini
stty-error-write-error = erreur d'écriture

# Chaînes de format de sortie
stty-output-speed = vitesse {$speed} bauds ;
Expand Down
5 changes: 4 additions & 1 deletion src/uu/stty/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

uucore::bin!(uu_stty);
// All output paths in uu_stty handle stdout write errors themselves and end
// with a newline (draining the line buffer), so the defensive exit-time flush
// is not necessary and would report write errors a second time.
uucore::bin!(uu_stty, no_flush);
92 changes: 54 additions & 38 deletions src/uu/stty/src/stty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,13 @@ use nix::sys::termios::{
use nix::{ioctl_read_bad, ioctl_write_ptr_bad};
use std::cmp::Ordering;
use std::fs::File;
use std::io::{self, Stdin, stdin, stdout};
use std::io::{self, Stdin, Write, stdin, stdout};
use std::num::IntErrorKind;
use std::os::fd::{AsFd, BorrowedFd};
use std::os::unix::fs::OpenOptionsExt;
use std::os::unix::io::{AsRawFd, RawFd};
use uucore::error::{FromIo, UError, UResult, USimpleError, UUsageError};
use thiserror::Error;
use uucore::error::{FromIo, UError, UResult, USimpleError, UUsageError, strip_errno};
use uucore::format_usage;
use uucore::parser::num_parser::ExtendedParser;
use uucore::translate;
Expand Down Expand Up @@ -550,17 +551,27 @@ fn check_flag_group<T>(flag: &Flag<T>, remove: bool) -> bool {
remove && flag.group.is_some()
}

fn print_special_setting(setting: &PrintSetting, fd: i32) -> nix::Result<()> {
fn print_special_setting(setting: &PrintSetting, fd: i32) -> UResult<()> {
match setting {
PrintSetting::Size => {
let mut size = TermSize::default();
unsafe { tiocgwinsz(fd, &raw mut size)? };
println!("{} {}", size.rows, size.columns);
writeln!(stdout(), "{} {}", size.rows, size.columns).map_err(SttyError::Write)?;
}
}
Ok(())
}

#[derive(Debug, Error)]
enum SttyError {
/// A write to stdout failed. Report instead of panicking the way
/// 'print!' family of macros does.
#[error("{}: {}", translate!("stty-error-write-error"), strip_errno(.0))]
Write(io::Error),
}

impl UError for SttyError {}

/// Handles line wrapping for stty output to fit within terminal width
struct WrappedPrinter {
width: usize,
Expand Down Expand Up @@ -591,29 +602,31 @@ impl WrappedPrinter {
}
}

fn print(&mut self, token: &str) {
fn print(&mut self, token: &str) -> UResult<()> {
let token_len = self.prefix().chars().count() + token.chars().count();
if self.current > 0 && self.current + token_len > self.width {
println!();
writeln!(stdout()).map_err(SttyError::Write)?;
self.current = 0;
self.first_in_line = true;
}

print!("{}{token}", self.prefix());
write!(stdout(), "{}{token}", self.prefix()).map_err(SttyError::Write)?;
self.current += token_len;
self.first_in_line = false;
Ok(())
}

fn prefix(&self) -> &str {
if self.first_in_line { "" } else { " " }
}

fn flush(&mut self) {
fn flush(&mut self) -> UResult<()> {
if self.current > 0 {
println!();
writeln!(stdout()).map_err(SttyError::Write)?;
self.current = 0;
self.first_in_line = false;
}
Ok(())
}
}

Expand All @@ -626,7 +639,7 @@ fn print_terminal_size(
opts: &Options,
window_size: Option<&TermSize>,
term_size: Option<&TermSize>,
) -> nix::Result<()> {
) -> UResult<()> {
// GNU linked against glibc 2.42 provides us baudrate 51 which panics cfgetospeed
#[cfg(not(target_os = "linux"))]
let speed = nix::sys::termios::cfgetospeed(termios);
Expand All @@ -653,7 +666,7 @@ fn print_terminal_size(
not(target_arch = "powerpc"),
not(all(target_arch = "powerpc64", target_endian = "big"))
))]
printer.print(&translate!("stty-output-speed", "speed" => speed));
printer.print(&translate!("stty-output-speed", "speed" => speed))?;

// Big-endian Linux PowerPC uses BaudRate enum, need to convert to display format
#[cfg(target_os = "linux")]
Expand All @@ -668,15 +681,15 @@ fn print_terminal_size(
.find(|(_, rate)| *rate == speed)
.map(|(text, _)| *text)
.unwrap_or("unknown");
printer.print(&translate!("stty-output-speed", "speed" => speed_str));
printer.print(&translate!("stty-output-speed", "speed" => speed_str))?;
}

// Other platforms need to use the baud rate enum, so printing the right value
// becomes slightly more complicated.
#[cfg(not(any(target_os = "linux", bsd)))]
for (text, baud_rate) in BAUD_RATES {
if *baud_rate == speed {
printer.print(&translate!("stty-output-speed", "speed" => (*text)));
printer.print(&translate!("stty-output-speed", "speed" => (*text)))?;
break;
}
}
Expand All @@ -685,7 +698,7 @@ fn print_terminal_size(
let term_size = term_size.as_ref().expect("terminal size should be set");
printer.print(
&translate!("stty-output-rows-columns", "rows" => term_size.rows, "columns" => term_size.columns),
);
)?;
}

#[cfg(any(target_os = "linux", target_os = "redox"))]
Expand All @@ -694,9 +707,9 @@ fn print_terminal_size(
// so we get the underlying libc::termios struct to get 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));
printer.print(&translate!("stty-output-line", "line" => line))?;
}
printer.flush();
printer.flush()?;
Ok(())
}

Expand Down Expand Up @@ -854,7 +867,7 @@ fn print_control_chars(
termios: &Termios,
opts: &Options,
term_size: Option<&TermSize>,
) -> nix::Result<()> {
) -> UResult<()> {
if !opts.all {
// Print only control chars that differ from sane defaults
let mut printer = WrappedPrinter::new(term_size);
Expand All @@ -866,10 +879,10 @@ fn print_control_chars(
printer.print(&format!(
"{text} = {};",
control_char_to_string(current_val)?
));
))?;
}
}
printer.flush();
printer.flush()?;
return Ok(());
}

Expand All @@ -878,28 +891,31 @@ fn print_control_chars(
printer.print(&format!(
"{text} = {};",
control_char_to_string(termios.control_chars[*cc_index as usize])?
));
))?;
}
printer.print(&translate!("stty-output-min-time",
"min" => termios.control_chars[S::VMIN as usize],
"time" => termios.control_chars[S::VTIME as usize]
));
printer.flush();
))?;
printer.flush()?;
Ok(())
}

fn print_in_save_format(termios: &Termios) {
print!(
fn print_in_save_format(termios: &Termios) -> UResult<()> {
write!(
stdout(),
"{:x}:{:x}:{:x}:{:x}",
termios.input_flags.bits(),
termios.output_flags.bits(),
termios.control_flags.bits(),
termios.local_flags.bits()
);
)
.map_err(SttyError::Write)?;
for cc in termios.control_chars {
print!(":{cc:x}");
write!(stdout(), ":{cc:x}").map_err(SttyError::Write)?;
}
println!();
writeln!(stdout()).map_err(SttyError::Write)?;
Ok(())
}

/// Gets terminal size using the tiocgwinsz ioctl system call.
Expand All @@ -909,9 +925,9 @@ fn get_terminal_size(fd: RawFd) -> nix::Result<TermSize> {
unsafe { tiocgwinsz(fd, &raw mut term_size) }.map(|_| term_size)
}

fn print_settings(termios: &Termios, opts: &Options) -> nix::Result<()> {
fn print_settings(termios: &Termios, opts: &Options) -> UResult<()> {
if opts.save {
print_in_save_format(termios);
print_in_save_format(termios)?;
} else {
let device_fd = opts.file.as_raw_fd();
let term_size = if opts.all {
Expand All @@ -929,10 +945,10 @@ fn print_settings(termios: &Termios, opts: &Options) -> nix::Result<()> {

print_terminal_size(termios, opts, window_size.as_ref(), term_size.as_ref())?;
print_control_chars(termios, opts, window_size.as_ref())?;
print_flags(termios, opts, CONTROL_FLAGS, window_size.as_ref());
print_flags(termios, opts, INPUT_FLAGS, window_size.as_ref());
print_flags(termios, opts, OUTPUT_FLAGS, window_size.as_ref());
print_flags(termios, opts, LOCAL_FLAGS, window_size.as_ref());
print_flags(termios, opts, CONTROL_FLAGS, window_size.as_ref())?;
print_flags(termios, opts, INPUT_FLAGS, window_size.as_ref())?;
print_flags(termios, opts, OUTPUT_FLAGS, window_size.as_ref())?;
print_flags(termios, opts, LOCAL_FLAGS, window_size.as_ref())?;
}
Ok(())
}
Expand All @@ -942,7 +958,7 @@ fn print_flags<T: TermiosFlag>(
opts: &Options,
flags: &[Flag<T>],
term_size: Option<&TermSize>,
) {
) -> UResult<()> {
let mut printer = WrappedPrinter::new(term_size);
for &Flag {
name,
Expand All @@ -958,17 +974,17 @@ fn print_flags<T: TermiosFlag>(
let val = flag.is_in(termios, group);
if group.is_some() {
if val && (!sane || opts.all) {
printer.print(name);
printer.print(name)?;
}
} else if opts.all || val != sane {
if !val {
printer.print(&format!("-{name}"));
printer.print(&format!("-{name}"))?;
continue;
}
printer.print(name);
printer.print(name)?;
}
}
printer.flush();
printer.flush()
}

/// Apply a single setting
Expand Down
20 changes: 20 additions & 0 deletions tests/by-util/test_stty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1759,3 +1759,23 @@ fn test_columns_env_wrapping() {
);
}
}

#[test]
#[cfg(target_os = "linux")]
fn test_write_error_to_dev_full() {
// stty must report write errors like GNU instead of panicking.
for args in [&[][..], &["--all"][..], &["--save"][..], &["size"][..]] {
let (path, _controller, _replica) = pty_path();
let dev_full = std::fs::OpenOptions::new()
.write(true)
.open("/dev/full")
.expect("/dev/full must exists on Linux");

new_ucmd!()
.args(&["--file", &path])
.args(args)
.set_stdout(dev_full)
.fails_with_code(1)
.stderr_only("stty: write error: No space left on device\n");
}
}
Loading