1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 19:47:45 +00:00

du/sort/od/stdbuf: make error handling of SIZE/BYTES/MODE arguments more consistent

* od: add stderr info for not yet implemented '--strings' flag
This commit is contained in:
Jan Scheer 2021-06-03 21:00:03 +02:00
parent ad26b7a042
commit db3ee61742
9 changed files with 153 additions and 93 deletions

View file

@ -42,7 +42,7 @@ mod options {
pub const NULL: &str = "0"; pub const NULL: &str = "0";
pub const ALL: &str = "all"; pub const ALL: &str = "all";
pub const APPARENT_SIZE: &str = "apparent-size"; pub const APPARENT_SIZE: &str = "apparent-size";
pub const BLOCK_SIZE: &str = "B"; pub const BLOCK_SIZE: &str = "block-size";
pub const BYTES: &str = "b"; pub const BYTES: &str = "b";
pub const TOTAL: &str = "c"; pub const TOTAL: &str = "c";
pub const MAX_DEPTH: &str = "d"; pub const MAX_DEPTH: &str = "d";
@ -211,18 +211,11 @@ fn get_file_info(path: &PathBuf) -> Option<FileInfo> {
} }
fn read_block_size(s: Option<&str>) -> usize { fn read_block_size(s: Option<&str>) -> usize {
if let Some(size_arg) = s { if let Some(s) = s {
match parse_size(size_arg) { parse_size(s).map_or_else(
Ok(v) => v, |e| crash!(1, "{}", format_error_message(e, s, options::BLOCK_SIZE)),
Err(e) => match e { |n| n,
ParseSizeError::ParseFailure(_) => { )
crash!(1, "invalid suffix in --block-size argument '{}'", size_arg)
}
ParseSizeError::SizeTooBig(_) => {
crash!(1, "--block-size argument '{}' too large", size_arg)
}
},
}
} else { } else {
for env_var in &["DU_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] { for env_var in &["DU_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] {
if let Ok(env_size) = env::var(env_var) { if let Ok(env_size) = env::var(env_var) {
@ -389,7 +382,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.arg( .arg(
Arg::with_name(options::BLOCK_SIZE) Arg::with_name(options::BLOCK_SIZE)
.short("B") .short("B")
.long("block-size") .long(options::BLOCK_SIZE)
.value_name("SIZE") .value_name("SIZE")
.help( .help(
"scale sizes by SIZE before printing them. \ "scale sizes by SIZE before printing them. \
@ -694,6 +687,16 @@ Try '{} --help' for more information.",
0 0
} }
fn format_error_message(error: ParseSizeError, s: &str, option: &str) -> String {
// NOTE:
// GNU's du echos affected flag, -B or --block-size (-t or --threshold), depending user's selection
// GNU's du distinguishs between "invalid (suffix in) argument"
match error {
ParseSizeError::ParseFailure(_) => format!("invalid --{} argument '{}'", option, s),
ParseSizeError::SizeTooBig(_) => format!("--{} argument '{}' too large", option, s),
}
}
#[cfg(test)] #[cfg(test)]
mod test_du { mod test_du {
#[allow(unused_imports)] #[allow(unused_imports)]

View file

@ -43,6 +43,7 @@ use crate::partialreader::*;
use crate::peekreader::*; use crate::peekreader::*;
use crate::prn_char::format_ascii_dump; use crate::prn_char::format_ascii_dump;
use clap::{self, AppSettings, Arg, ArgMatches}; use clap::{self, AppSettings, Arg, ArgMatches};
use uucore::parse_size::ParseSizeError;
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
@ -130,15 +131,16 @@ impl OdOptions {
} }
}; };
let mut skip_bytes = match matches.value_of(options::SKIP_BYTES) { if matches.occurrences_of(options::STRINGS) > 0 {
None => 0, crash!(1, "Option '{}' not yet implemented.", options::STRINGS);
Some(s) => match parse_number_of_bytes(&s) { }
Ok(i) => i,
Err(_) => { let mut skip_bytes = matches.value_of(options::SKIP_BYTES).map_or(0, |s| {
return Err(format!("Invalid argument --skip-bytes={}", s)); parse_number_of_bytes(s).map_or_else(
} |e| crash!(1, "{}", format_error_message(e, s, options::SKIP_BYTES)),
}, |n| n,
}; )
});
let mut label: Option<usize> = None; let mut label: Option<usize> = None;
@ -161,11 +163,16 @@ impl OdOptions {
} }
}; };
let mut line_bytes = match matches.value_of(options::WIDTH) { let mut line_bytes = matches.value_of(options::WIDTH).map_or(16, |s| {
None => 16, if matches.occurrences_of(options::WIDTH) == 0 {
Some(_) if matches.occurrences_of(options::WIDTH) == 0 => 16, return 16;
Some(s) => s.parse::<usize>().unwrap_or(0), };
}; parse_number_of_bytes(s).map_or_else(
|e| crash!(1, "{}", format_error_message(e, s, options::WIDTH)),
|n| n,
)
});
let min_bytes = formats.iter().fold(1, |max, next| { let min_bytes = formats.iter().fold(1, |max, next| {
cmp::max(max, next.formatter_item_info.byte_size) cmp::max(max, next.formatter_item_info.byte_size)
}); });
@ -176,15 +183,12 @@ impl OdOptions {
let output_duplicates = matches.is_present(options::OUTPUT_DUPLICATES); let output_duplicates = matches.is_present(options::OUTPUT_DUPLICATES);
let read_bytes = match matches.value_of(options::READ_BYTES) { let read_bytes = matches.value_of(options::READ_BYTES).map(|s| {
None => None, parse_number_of_bytes(s).map_or_else(
Some(s) => match parse_number_of_bytes(&s) { |e| crash!(1, "{}", format_error_message(e, s, options::READ_BYTES)),
Ok(i) => Some(i), |n| n,
Err(_) => { )
return Err(format!("Invalid argument --read-bytes={}", s)); });
}
},
};
let radix = match matches.value_of(options::ADDRESS_RADIX) { let radix = match matches.value_of(options::ADDRESS_RADIX) {
None => Radix::Octal, None => Radix::Octal,
@ -265,7 +269,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.short("S") .short("S")
.long(options::STRINGS) .long(options::STRINGS)
.help( .help(
"output strings of at least BYTES graphic chars. 3 is assumed when \ "NotImplemented: output strings of at least BYTES graphic chars. 3 is assumed when \
BYTES is not specified.", BYTES is not specified.",
) )
.default_value("3") .default_value("3")
@ -466,8 +470,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let od_options = match OdOptions::new(clap_matches, args) { let od_options = match OdOptions::new(clap_matches, args) {
Err(s) => { Err(s) => {
show_usage_error!("{}", s); crash!(1, "{}", s);
return 1;
} }
Ok(o) => o, Ok(o) => o,
}; };
@ -649,3 +652,13 @@ fn open_input_peek_reader(
let pr = PartialReader::new(mf, skip_bytes, read_bytes); let pr = PartialReader::new(mf, skip_bytes, read_bytes);
PeekReader::new(pr) PeekReader::new(pr)
} }
fn format_error_message(error: ParseSizeError, s: &str, option: &str) -> String {
// NOTE:
// GNU's od echos affected flag, -N or --read-bytes (-j or --skip-bytes, etc.), depending user's selection
// GNU's od distinguishs between "invalid (suffix in) argument"
match error {
ParseSizeError::ParseFailure(_) => format!("invalid --{} argument '{}'", option, s),
ParseSizeError::SizeTooBig(_) => format!("--{} argument '{}' too large", option, s),
}
}

View file

@ -1,4 +1,6 @@
pub fn parse_number_of_bytes(s: &str) -> Result<usize, &'static str> { use uucore::parse_size::{parse_size, ParseSizeError};
pub fn parse_number_of_bytes(s: &str) -> Result<usize, ParseSizeError> {
let mut start = 0; let mut start = 0;
let mut len = s.len(); let mut len = s.len();
let mut radix = 16; let mut radix = 16;
@ -9,10 +11,7 @@ pub fn parse_number_of_bytes(s: &str) -> Result<usize, &'static str> {
} else if s.starts_with('0') { } else if s.starts_with('0') {
radix = 8; radix = 8;
} else { } else {
return match uucore::parse_size::parse_size(&s[start..]) { return parse_size(&s[start..]);
Ok(n) => Ok(n),
Err(_) => Err("parse failed"),
};
} }
let mut ends_with = s.chars().rev(); let mut ends_with = s.chars().rev();
@ -60,35 +59,33 @@ pub fn parse_number_of_bytes(s: &str) -> Result<usize, &'static str> {
Some('P') => 1000 * 1000 * 1000 * 1000 * 1000, Some('P') => 1000 * 1000 * 1000 * 1000 * 1000,
#[cfg(target_pointer_width = "64")] #[cfg(target_pointer_width = "64")]
Some('E') => 1000 * 1000 * 1000 * 1000 * 1000 * 1000, Some('E') => 1000 * 1000 * 1000 * 1000 * 1000 * 1000,
_ => return Err("parse failed"), _ => return Err(ParseSizeError::ParseFailure(s.to_string())),
} }
} }
_ => {} _ => {}
} }
match usize::from_str_radix(&s[start..len], radix) { let factor = match usize::from_str_radix(&s[start..len], radix) {
Ok(i) => Ok(i * multiply), Ok(f) => f,
Err(_) => Err("parse failed"), Err(e) => return Err(ParseSizeError::ParseFailure(e.to_string())),
} };
} factor
.checked_mul(multiply)
#[allow(dead_code)] .ok_or(ParseSizeError::SizeTooBig(s.to_string()))
fn parse_number_of_bytes_str(s: &str) -> Result<usize, &'static str> {
parse_number_of_bytes(&String::from(s))
} }
#[test] #[test]
fn test_parse_number_of_bytes() { fn test_parse_number_of_bytes() {
// octal input // octal input
assert_eq!(8, parse_number_of_bytes_str("010").unwrap()); assert_eq!(8, parse_number_of_bytes("010").unwrap());
assert_eq!(8 * 512, parse_number_of_bytes_str("010b").unwrap()); assert_eq!(8 * 512, parse_number_of_bytes("010b").unwrap());
assert_eq!(8 * 1024, parse_number_of_bytes_str("010k").unwrap()); assert_eq!(8 * 1024, parse_number_of_bytes("010k").unwrap());
assert_eq!(8 * 1048576, parse_number_of_bytes_str("010m").unwrap()); assert_eq!(8 * 1048576, parse_number_of_bytes("010m").unwrap());
// hex input // hex input
assert_eq!(15, parse_number_of_bytes_str("0xf").unwrap()); assert_eq!(15, parse_number_of_bytes("0xf").unwrap());
assert_eq!(15, parse_number_of_bytes_str("0XF").unwrap()); assert_eq!(15, parse_number_of_bytes("0XF").unwrap());
assert_eq!(27, parse_number_of_bytes_str("0x1b").unwrap()); assert_eq!(27, parse_number_of_bytes("0x1b").unwrap());
assert_eq!(16 * 1024, parse_number_of_bytes_str("0x10k").unwrap()); assert_eq!(16 * 1024, parse_number_of_bytes("0x10k").unwrap());
assert_eq!(16 * 1048576, parse_number_of_bytes_str("0x10m").unwrap()); assert_eq!(16 * 1048576, parse_number_of_bytes("0x10m").unwrap());
} }

View file

@ -1148,12 +1148,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
settings.buffer_size = matches settings.buffer_size = matches
.value_of(OPT_BUF_SIZE) .value_of(OPT_BUF_SIZE)
.map(|v| match GlobalSettings::parse_byte_count(v) { .map_or(DEFAULT_BUF_SIZE, |s| {
Ok(n) => n, GlobalSettings::parse_byte_count(s).map_or_else(
Err(ParseSizeError::ParseFailure(_)) => crash!(2, "invalid -S argument '{}'", v), |e| crash!(2, "{}", format_error_message(e, s, OPT_BUF_SIZE)),
Err(ParseSizeError::SizeTooBig(_)) => crash!(2, "-S argument '{}' too large", v), |n| n,
}) )
.unwrap_or(DEFAULT_BUF_SIZE); });
settings.tmp_dir = matches settings.tmp_dir = matches
.value_of(OPT_TMP_DIR) .value_of(OPT_TMP_DIR)
@ -1555,6 +1555,16 @@ fn open(path: impl AsRef<OsStr>) -> Box<dyn Read + Send> {
} }
} }
fn format_error_message(error: ParseSizeError, s: &str, option: &str) -> String {
// NOTE:
// GNU's sort echos affected flag, -S or --buffer-size, depending user's selection
// GNU's sort distinguishs between "invalid (suffix in) argument"
match error {
ParseSizeError::ParseFailure(_) => format!("invalid --{} argument '{}'", option, s),
ParseSizeError::SizeTooBig(_) => format!("--{} argument '{}' too large", option, s),
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
@ -1659,7 +1669,7 @@ mod tests {
("10T", 10 * 1024 * 1024 * 1024 * 1024), ("10T", 10 * 1024 * 1024 * 1024 * 1024),
("1b", 1), ("1b", 1),
("1024b", 1024), ("1024b", 1024),
("1024Mb", 1024 * 1024 * 1024), // TODO: This might not be what GNU `sort` does? ("1024Mb", 1024 * 1024 * 1024), // NOTE: This might not be how GNU `sort` behaves for 'Mb'
("1", 1024), // K is default ("1", 1024), // K is default
("50", 50 * 1024), ("50", 50 * 1024),
("K", 1024), ("K", 1024),

View file

@ -118,13 +118,10 @@ fn check_option(matches: &ArgMatches, name: &str) -> Result<BufferType, ProgramO
Ok(BufferType::Line) Ok(BufferType::Line)
} }
} }
x => { x => parse_size(x).map_or_else(
let size = match parse_size(x) { |e| crash!(125, "invalid mode {}", e),
Ok(m) => m, |m| Ok(BufferType::Size(m)),
Err(e) => return Err(ProgramOptionsError(format!("invalid mode {}", e))), ),
};
Ok(BufferType::Size(size))
}
}, },
None => Ok(BufferType::Default), None => Ok(BufferType::Default),
} }

View file

@ -80,7 +80,7 @@ fn test_du_invalid_size() {
.arg("/tmp") .arg("/tmp")
.fails() .fails()
.code_is(1) .code_is(1)
.stderr_only("du: invalid suffix in --block-size argument '1fb4t'"); .stderr_only("du: invalid --block-size argument '1fb4t'");
#[cfg(not(target_pointer_width = "128"))] #[cfg(not(target_pointer_width = "128"))]
new_ucmd!() new_ucmd!()
.arg("--block-size=1Y") .arg("--block-size=1Y")

View file

@ -804,3 +804,37 @@ fn test_traditional_only_label() {
", ",
)); ));
} }
#[test]
fn test_od_invalid_bytes() {
const INVALID_SIZE: &str = "1fb4t";
const BIG_SIZE: &str = "1Y";
let input: [u8; 8] = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
let options = [
"--read-bytes",
"--skip-bytes",
"--width",
// "--strings", // TODO: consider testing here once '--strings' is implemented
];
for option in &options {
new_ucmd!()
.arg(format!("{}={}", option, INVALID_SIZE))
.run_piped_stdin(&input[..])
.failure()
.code_is(1)
.stderr_only(format!(
"od: invalid {} argument '{}'",
option, INVALID_SIZE
));
#[cfg(not(target_pointer_width = "128"))]
new_ucmd!()
.arg(format!("{}={}", option, BIG_SIZE))
.run_piped_stdin(&input[..])
.failure()
.code_is(1)
.stderr_only(format!("od: {} argument '{}' too large", option, BIG_SIZE));
}
}

View file

@ -57,7 +57,7 @@ fn test_invalid_buffer_size() {
.fails() .fails()
.code_is(2) .code_is(2)
.stderr_only(format!( .stderr_only(format!(
"sort: invalid -S argument '{}'", "sort: invalid --buffer-size argument '{}'",
invalid_buffer_size invalid_buffer_size
)); ));
} }
@ -69,7 +69,7 @@ fn test_invalid_buffer_size() {
.arg("ext_sort.txt") .arg("ext_sort.txt")
.fails() .fails()
.code_is(2) .code_is(2)
.stderr_only("sort: -S argument '1Y' too large"); .stderr_only("sort: --buffer-size argument '1Y' too large");
#[cfg(target_pointer_width = "32")] #[cfg(target_pointer_width = "32")]
{ {
@ -82,7 +82,10 @@ fn test_invalid_buffer_size() {
.arg("ext_sort.txt") .arg("ext_sort.txt")
.fails() .fails()
.code_is(2) .code_is(2)
.stderr_only(format!("sort: -S argument '{}' too large", buffer_size)); .stderr_only(format!(
"sort: --buffer-size argument '{}' too large",
buffer_size
));
} }
} }
} }

View file

@ -57,15 +57,18 @@ fn test_stdbuf_line_buffering_stdin_fails() {
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
#[test] #[test]
fn test_stdbuf_invalid_mode_fails() { fn test_stdbuf_invalid_mode_fails() {
// TODO: GNU's `stdbuf` (8.32) does not return "\nTry 'stdbuf --help' for more information." let options = ["--input", "--output", "--error"];
// for invalid modes. for option in &options {
new_ucmd!() new_ucmd!()
.args(&["-i", "1024R", "head"]) .args(&[*option, "1024R", "head"])
.fails() .fails()
.stderr_contains("stdbuf: invalid mode 1024R"); .code_is(125)
#[cfg(not(target_pointer_width = "128"))] .stderr_only("stdbuf: invalid mode 1024R");
new_ucmd!() #[cfg(not(target_pointer_width = "128"))]
.args(&["--error", "1Y", "head"]) new_ucmd!()
.fails() .args(&[*option, "1Y", "head"])
.stderr_contains("stdbuf: invalid mode 1Y: Value too large to be stored in data type"); .fails()
.code_is(125)
.stderr_contains("stdbuf: invalid mode 1Y: Value too large for defined data type");
}
} }