diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index df72ea89da..fed0d6d206 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 e581ec2ada..4071a3870b 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 62aa07dae5..845b8581aa 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")