From 39847a741a0fd24068ab0d15138c67e0209df693 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Mon, 20 Jan 2025 12:35:11 -0500 Subject: [PATCH 1/2] Add ParseSizeError::PhysicalMem enum variant --- src/uu/dd/src/parseargs.rs | 4 +--- src/uu/df/src/df.rs | 1 + src/uu/du/src/du.rs | 4 +++- src/uu/od/src/od.rs | 4 +++- src/uu/sort/src/sort.rs | 4 +++- src/uucore/src/lib/parser/parse_size.rs | 9 ++++++++- 6 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/uu/dd/src/parseargs.rs b/src/uu/dd/src/parseargs.rs index 59836b1a1e4..e26b3495316 100644 --- a/src/uu/dd/src/parseargs.rs +++ b/src/uu/dd/src/parseargs.rs @@ -517,9 +517,7 @@ fn parse_bytes_no_x(full: &str, s: &str) -> Result { (None, None, None) => match parser.parse_u64(s) { Ok(n) => (n, 1), Err(ParseSizeError::SizeTooBig(_)) => (u64::MAX, 1), - Err(ParseSizeError::InvalidSuffix(_) | ParseSizeError::ParseFailure(_)) => { - return Err(ParseError::InvalidNumber(full.to_string())) - } + Err(_) => return Err(ParseError::InvalidNumber(full.to_string())), }, (Some(i), None, None) => (parse_bytes_only(s, i)?, 1), (None, Some(i), None) => (parse_bytes_only(s, i)?, 2), diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index 092c8381290..8602d8af7af 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -189,6 +189,7 @@ impl Options { .to_string(), ), ParseSizeError::ParseFailure(s) => OptionsError::InvalidBlockSize(s), + ParseSizeError::PhysicalMem(s) => OptionsError::InvalidBlockSize(s), })?, header_mode: { if matches.get_flag(OPT_HUMAN_READABLE_BINARY) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 2392497a935..bd017f1d515 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -1120,7 +1120,9 @@ fn format_error_message(error: &ParseSizeError, s: &str, option: &str) -> String ParseSizeError::InvalidSuffix(_) => { format!("invalid suffix in --{} argument {}", option, s.quote()) } - ParseSizeError::ParseFailure(_) => format!("invalid --{} argument {}", option, s.quote()), + ParseSizeError::ParseFailure(_) | ParseSizeError::PhysicalMem(_) => { + format!("invalid --{} argument {}", option, s.quote()) + } ParseSizeError::SizeTooBig(_) => format!("--{} argument {} too large", option, s.quote()), } } diff --git a/src/uu/od/src/od.rs b/src/uu/od/src/od.rs index 6dd75d30792..fcb72c1ae70 100644 --- a/src/uu/od/src/od.rs +++ b/src/uu/od/src/od.rs @@ -626,7 +626,9 @@ fn format_error_message(error: &ParseSizeError, s: &str, option: &str) -> String ParseSizeError::InvalidSuffix(_) => { format!("invalid suffix in --{} argument {}", option, s.quote()) } - ParseSizeError::ParseFailure(_) => format!("invalid --{} argument {}", option, s.quote()), + ParseSizeError::ParseFailure(_) | ParseSizeError::PhysicalMem(_) => { + format!("invalid --{} argument {}", option, s.quote()) + } ParseSizeError::SizeTooBig(_) => format!("--{} argument {} too large", option, s.quote()), } } diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 0c39c24421b..df72ea89daa 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1845,7 +1845,9 @@ fn format_error_message(error: &ParseSizeError, s: &str, option: &str) -> String ParseSizeError::InvalidSuffix(_) => { format!("invalid suffix in --{} argument {}", option, s.quote()) } - ParseSizeError::ParseFailure(_) => format!("invalid --{} argument {}", option, s.quote()), + ParseSizeError::ParseFailure(_) | ParseSizeError::PhysicalMem(_) => { + format!("invalid --{} argument {}", option, s.quote()) + } ParseSizeError::SizeTooBig(_) => format!("--{} argument {} too large", option, s.quote()), } } diff --git a/src/uucore/src/lib/parser/parse_size.rs b/src/uucore/src/lib/parser/parse_size.rs index 9247ad378e5..e581ec2adab 100644 --- a/src/uucore/src/lib/parser/parse_size.rs +++ b/src/uucore/src/lib/parser/parse_size.rs @@ -327,6 +327,9 @@ pub enum ParseSizeError { /// Overflow SizeTooBig(String), + + /// Could not determine total physical memory size. + PhysicalMem(String), } impl Error for ParseSizeError { @@ -335,6 +338,7 @@ impl Error for ParseSizeError { Self::InvalidSuffix(ref s) => s, Self::ParseFailure(ref s) => s, Self::SizeTooBig(ref s) => s, + Self::PhysicalMem(ref s) => s, } } } @@ -342,7 +346,10 @@ impl Error for ParseSizeError { impl fmt::Display for ParseSizeError { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { let s = match self { - Self::InvalidSuffix(s) | Self::ParseFailure(s) | Self::SizeTooBig(s) => s, + Self::InvalidSuffix(s) + | Self::ParseFailure(s) + | Self::SizeTooBig(s) + | Self::PhysicalMem(s) => s, }; write!(f, "{s}") } From 94c772c0822f20a10ca267bfd16315c23289f063 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Mon, 20 Jan 2025 12:36:10 -0500 Subject: [PATCH 2/2] sort: support percent arguments to -S option Add support for parsing percent arguments to the `-S` option. The given percentage specifies a percentage of the total physical memory. For Linux, the total physical memory is read from `/proc/meminfo`. The feature is not yet implemented for other systems. In order to implement the feature, the `uucore::parser::parse_size` function was updated to recognize strings of the form `NNN%`. Fixes #3500 --- src/uu/sort/src/sort.rs | 2 +- src/uucore/src/lib/parser/parse_size.rs | 84 ++++++++++++++++++++++++- tests/by-util/test_sort.rs | 13 ++++ 3 files changed, 97 insertions(+), 2 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index df72ea89daa..fed0d6d2062 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -288,7 +288,7 @@ impl GlobalSettings { // GNU sort (8.32) invalid: b, B, 1B, p, e, z, y let size = Parser::default() .with_allow_list(&[ - "b", "k", "K", "m", "M", "g", "G", "t", "T", "P", "E", "Z", "Y", + "b", "k", "K", "m", "M", "g", "G", "t", "T", "P", "E", "Z", "Y", "%", ]) .with_default_unit("K") .with_b_byte_count(true) diff --git a/src/uucore/src/lib/parser/parse_size.rs b/src/uucore/src/lib/parser/parse_size.rs index e581ec2adab..4071a3870b8 100644 --- a/src/uucore/src/lib/parser/parse_size.rs +++ b/src/uucore/src/lib/parser/parse_size.rs @@ -8,10 +8,70 @@ use std::error::Error; use std::fmt; -use std::num::IntErrorKind; +#[cfg(target_os = "linux")] +use std::io::BufRead; +use std::num::{IntErrorKind, ParseIntError}; use crate::display::Quotable; +/// Error arising from trying to compute system memory. +enum SystemError { + IOError, + ParseError, + NotFound, +} + +impl From for SystemError { + fn from(_: std::io::Error) -> Self { + Self::IOError + } +} + +impl From for SystemError { + fn from(_: ParseIntError) -> Self { + Self::ParseError + } +} + +/// Get the total number of bytes of physical memory. +/// +/// The information is read from the `/proc/meminfo` file. +/// +/// # Errors +/// +/// If there is a problem reading the file or finding the appropriate +/// entry in the file. +#[cfg(target_os = "linux")] +fn total_physical_memory() -> Result { + // On Linux, the `/proc/meminfo` file has a table with information + // about memory usage. For example, + // + // MemTotal: 7811500 kB + // MemFree: 1487876 kB + // MemAvailable: 3857232 kB + // ... + // + // We just need to extract the number of `MemTotal` + let table = std::fs::read("/proc/meminfo")?; + for line in table.lines() { + let line = line?; + if line.starts_with("MemTotal:") && line.ends_with("kB") { + let num_kilobytes: u128 = line[9..line.len() - 2].trim().parse()?; + let num_bytes = 1024 * num_kilobytes; + return Ok(num_bytes); + } + } + Err(SystemError::NotFound) +} + +/// Get the total number of bytes of physical memory. +/// +/// TODO Implement this for non-Linux systems. +#[cfg(not(target_os = "linux"))] +fn total_physical_memory() -> Result { + Err(SystemError::NotFound) +} + /// Parser for sizes in SI or IEC units (multiples of 1000 or 1024 bytes). /// /// The [`Parser::parse`] function performs the parse. @@ -133,6 +193,16 @@ impl<'parser> Parser<'parser> { } } + // Special case: for percentage, just compute the given fraction + // of the total physical memory on the machine, if possible. + if unit == "%" { + let number: u128 = Self::parse_number(&numeric_string, 10, size)?; + return match total_physical_memory() { + Ok(total) => Ok((number / 100) * total), + Err(_) => Err(ParseSizeError::PhysicalMem(size.to_string())), + }; + } + // Compute the factor the unit represents. // empty string means the factor is 1. // @@ -688,4 +758,16 @@ mod tests { assert_eq!(Ok(94722), parse_size_u64("0x17202")); assert_eq!(Ok(44251 * 1024), parse_size_u128("0xACDBK")); } + + #[test] + #[cfg(target_os = "linux")] + fn parse_percent() { + assert!(parse_size_u64("0%").is_ok()); + assert!(parse_size_u64("50%").is_ok()); + assert!(parse_size_u64("100%").is_ok()); + assert!(parse_size_u64("100000%").is_ok()); + assert!(parse_size_u64("-1%").is_err()); + assert!(parse_size_u64("1.0%").is_err()); + assert!(parse_size_u64("0x1%").is_err()); + } } diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 62aa07dae5d..845b8581aaa 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -29,6 +29,10 @@ fn test_helper(file_name: &str, possible_args: &[&str]) { #[test] fn test_buffer_sizes() { + #[cfg(target_os = "linux")] + let buffer_sizes = ["0", "50K", "50k", "1M", "100M", "0%", "10%"]; + // TODO Percentage sizes are not yet supported beyond Linux. + #[cfg(not(target_os = "linux"))] let buffer_sizes = ["0", "50K", "50k", "1M", "100M"]; for buffer_size in &buffer_sizes { TestScenario::new(util_name!()) @@ -73,6 +77,15 @@ fn test_invalid_buffer_size() { .code_is(2) .stderr_only("sort: invalid suffix in --buffer-size argument '100f'\n"); + // TODO Percentage sizes are not yet supported beyond Linux. + #[cfg(target_os = "linux")] + new_ucmd!() + .arg("-S") + .arg("0x123%") + .fails() + .code_is(2) + .stderr_only("sort: invalid --buffer-size argument '0x123%'\n"); + new_ucmd!() .arg("-n") .arg("-S")