Skip to content
Merged
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
102 changes: 92 additions & 10 deletions src/uu/stty/src/stty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@
// file that was distributed with this source code.

// spell-checker:ignore clocal erange tcgetattr tcsetattr tcsanow tiocgwinsz tiocswinsz cfgetospeed cfsetospeed ushort vmin vtime cflag lflag ispeed ospeed
// spell-checker:ignore tcsadrain

mod flags;

use crate::flags::AllFlags;
use clap::{Arg, ArgAction, ArgMatches, Command};
use nix::libc::{O_NONBLOCK, TIOCGWINSZ, TIOCSWINSZ, c_ushort};
use nix::sys::termios::{
ControlFlags, InputFlags, LocalFlags, OutputFlags, SpecialCharacterIndices, Termios,
ControlFlags, InputFlags, LocalFlags, OutputFlags, SetArg, SpecialCharacterIndices, Termios,
cfgetospeed, cfsetospeed, tcgetattr, tcsetattr,
};
use nix::{ioctl_read_bad, ioctl_write_ptr_bad};
use std::collections::HashMap;
use std::fs::File;
use std::io::{self, Stdout, stdout};
use std::num::IntErrorKind;
use std::os::fd::{AsFd, BorrowedFd};
use std::os::unix::fs::OpenOptionsExt;
use std::os::unix::io::{AsRawFd, RawFd};
Expand Down Expand Up @@ -112,6 +114,7 @@
enum SpecialSetting {
Rows(u16),
Cols(u16),
Line(u8),
}

enum PrintSetting {
Expand Down Expand Up @@ -238,6 +241,7 @@
));
}

let mut set_arg = SetArg::TCSADRAIN;
let mut valid_args: Vec<ArgOptions> = Vec::new();

if let Some(args) = &opts.settings {
Expand Down Expand Up @@ -299,7 +303,59 @@
));
}
}
// baud rate setting
} else if *arg == "line" {
match args_iter.next() {
Some(line) => match parse_u8_or_err(line) {
Ok(n) => valid_args.push(ArgOptions::Special(SpecialSetting::Line(n))),

Check warning on line 309 in src/uu/stty/src/stty.rs

View check run for this annotation

Codecov / codecov/patch

src/uu/stty/src/stty.rs#L309

Added line #L309 was not covered by tests
Err(e) => return Err(USimpleError::new(1, e)),
},
None => {
return Err(USimpleError::new(
1,
get_message_with_args(
"stty-error-missing-argument",
HashMap::from([("arg".to_string(), arg.to_string())]),
),
));
}
}
} else if *arg == "min" {
match args_iter.next() {
Some(min) => match parse_u8_or_err(min) {
Ok(n) => {
valid_args
.push(ArgOptions::Mapping((SpecialCharacterIndices::VMIN, n)));
}

Check warning on line 328 in src/uu/stty/src/stty.rs

View check run for this annotation

Codecov / codecov/patch

src/uu/stty/src/stty.rs#L325-L328

Added lines #L325 - L328 were not covered by tests
Err(e) => return Err(USimpleError::new(1, e)),
},
None => {
return Err(USimpleError::new(
1,
get_message_with_args(
"stty-error-missing-argument",
HashMap::from([("arg".to_string(), arg.to_string())]),
),
));
}
}
} else if *arg == "time" {
match args_iter.next() {
Some(time) => match parse_u8_or_err(time) {
Ok(n) => valid_args
.push(ArgOptions::Mapping((SpecialCharacterIndices::VTIME, n))),

Check warning on line 345 in src/uu/stty/src/stty.rs

View check run for this annotation

Codecov / codecov/patch

src/uu/stty/src/stty.rs#L344-L345

Added lines #L344 - L345 were not covered by tests
Err(e) => return Err(USimpleError::new(1, e)),
},
None => {
return Err(USimpleError::new(
1,
get_message_with_args(
"stty-error-missing-argument",
HashMap::from([("arg".to_string(), arg.to_string())]),
),
));
}
}
// baud rate setting
} else if let Some(baud_flag) = string_to_baud(arg) {
valid_args.push(ArgOptions::Flags(baud_flag));
// non control char flag
Expand Down Expand Up @@ -365,6 +421,10 @@
),
));
}
} else if *arg == "drain" {
set_arg = SetArg::TCSADRAIN;

Check warning on line 425 in src/uu/stty/src/stty.rs

View check run for this annotation

Codecov / codecov/patch

src/uu/stty/src/stty.rs#L425

Added line #L425 was not covered by tests
} else if *arg == "-drain" {
set_arg = SetArg::TCSANOW;

Check warning on line 427 in src/uu/stty/src/stty.rs

View check run for this annotation

Codecov / codecov/patch

src/uu/stty/src/stty.rs#L427

Added line #L427 was not covered by tests
} else if *arg == "size" {
valid_args.push(ArgOptions::Print(PrintSetting::Size));
// not a valid option
Expand All @@ -388,19 +448,15 @@
ArgOptions::Mapping(mapping) => apply_char_mapping(&mut termios, mapping),
ArgOptions::Flags(flag) => apply_setting(&mut termios, flag),
ArgOptions::Special(setting) => {
apply_special_setting(setting, opts.file.as_raw_fd())?;
apply_special_setting(&mut termios, setting, opts.file.as_raw_fd())?;

Check warning on line 451 in src/uu/stty/src/stty.rs

View check run for this annotation

Codecov / codecov/patch

src/uu/stty/src/stty.rs#L451

Added line #L451 was not covered by tests
}
ArgOptions::Print(setting) => {
print_special_setting(setting, opts.file.as_raw_fd())?;
}
}
}
tcsetattr(
opts.file.as_fd(),
nix::sys::termios::SetArg::TCSANOW,
&termios,
)
.expect("Could not write terminal attributes");
tcsetattr(opts.file.as_fd(), set_arg, &termios)
.expect("Could not write terminal attributes");

Check warning on line 459 in src/uu/stty/src/stty.rs

View check run for this annotation

Codecov / codecov/patch

src/uu/stty/src/stty.rs#L458-L459

Added lines #L458 - L459 were not covered by tests
} else {
// TODO: Figure out the right error message for when tcgetattr fails
let termios = tcgetattr(opts.file.as_fd()).expect("Could not get terminal attributes");
Expand All @@ -409,6 +465,21 @@
Ok(())
}

// GNU uses different error messages if values overflow or underflow a u8
// this function returns the appropriate error message in the case of overflow or underflow, or u8 on success
fn parse_u8_or_err(arg: &str) -> Result<u8, String> {
arg.parse::<u8>().map_err(|e| match e.kind() {
IntErrorKind::PosOverflow => get_message_with_args(
"stty-error-invalid-integer-argument-value-too-large",
HashMap::from([("value".to_string(), format!("'{}'", arg))]),
),
_ => get_message_with_args(
"stty-error-invalid-integer-argument",
HashMap::from([("value".to_string(), format!("'{}'", arg))]),
),
})
}

// GNU uses an unsigned 32 bit integer for row/col sizes, but then wraps around 16 bits
// this function returns Some(n), where n is a u16 row/col size, or None if the string arg cannot be parsed as a u32
fn parse_rows_cols(arg: &str) -> Option<u16> {
Expand Down Expand Up @@ -745,12 +816,23 @@
termios.control_chars[mapping.0 as usize] = mapping.1;
}

fn apply_special_setting(setting: &SpecialSetting, fd: i32) -> nix::Result<()> {
fn apply_special_setting(
_termios: &mut Termios,
setting: &SpecialSetting,
fd: i32,
) -> nix::Result<()> {

Check warning on line 823 in src/uu/stty/src/stty.rs

View check run for this annotation

Codecov / codecov/patch

src/uu/stty/src/stty.rs#L819-L823

Added lines #L819 - L823 were not covered by tests
let mut size = TermSize::default();
unsafe { tiocgwinsz(fd, &raw mut size)? };
match setting {
SpecialSetting::Rows(n) => size.rows = *n,
SpecialSetting::Cols(n) => size.columns = *n,
SpecialSetting::Line(_n) => {

Check warning on line 829 in src/uu/stty/src/stty.rs

View check run for this annotation

Codecov / codecov/patch

src/uu/stty/src/stty.rs#L829

Added line #L829 was not covered by tests
// nix only defines Termios's `line_discipline` field on these platforms
#[cfg(any(target_os = "linux", target_os = "android"))]
{
_termios.line_discipline = *_n;
}

Check warning on line 834 in src/uu/stty/src/stty.rs

View check run for this annotation

Codecov / codecov/patch

src/uu/stty/src/stty.rs#L832-L834

Added lines #L832 - L834 were not covered by tests
}
}
unsafe { tiocswinsz(fd, &raw mut size)? };
Ok(())
Expand Down
52 changes: 52 additions & 0 deletions tests/by-util/test_stty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,3 +241,55 @@ fn row_column_sizes() {
.fails()
.stderr_contains("missing argument to 'rows'");
}

#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn line() {
new_ucmd!()
.args(&["line"])
.fails()
.stderr_contains("missing argument to 'line'");

new_ucmd!()
.args(&["line", "-1"])
.fails()
.stderr_contains("invalid integer argument: '-1'");

new_ucmd!()
.args(&["line", "256"])
.fails()
.stderr_contains("invalid integer argument: '256'");
}

#[test]
fn min_and_time() {
new_ucmd!()
.args(&["min"])
.fails()
.stderr_contains("missing argument to 'min'");

new_ucmd!()
.args(&["time"])
.fails()
.stderr_contains("missing argument to 'time'");

new_ucmd!()
.args(&["min", "-1"])
.fails()
.stderr_contains("invalid integer argument: '-1'");

new_ucmd!()
.args(&["time", "-1"])
.fails()
.stderr_contains("invalid integer argument: '-1'");

new_ucmd!()
.args(&["min", "256"])
.fails()
.stderr_contains("invalid integer argument: '256': Value too large for defined data type");

new_ucmd!()
.args(&["time", "256"])
.fails()
.stderr_contains("invalid integer argument: '256': Value too large for defined data type");
}
Loading