Skip to content

Commit

Permalink
sort: support percent arguments to -S option
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jfinkels committed Jan 20, 2025
1 parent 39847a7 commit 94c772c
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 2 deletions.
2 changes: 1 addition & 1 deletion src/uu/sort/src/sort.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
84 changes: 83 additions & 1 deletion src/uucore/src/lib/parser/parse_size.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::io::Error> for SystemError {
fn from(_: std::io::Error) -> Self {
Self::IOError
}
}

impl From<ParseIntError> 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<u128, SystemError> {
// 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<u128, SystemError> {
Err(SystemError::NotFound)
}

/// Parser for sizes in SI or IEC units (multiples of 1000 or 1024 bytes).
///
/// The [`Parser::parse`] function performs the parse.
Expand Down Expand Up @@ -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.
//
Expand Down Expand Up @@ -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());
}
}
13 changes: 13 additions & 0 deletions tests/by-util/test_sort.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!())
Expand Down Expand Up @@ -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")
Expand Down

0 comments on commit 94c772c

Please sign in to comment.