Skip to content

Commit ceb0785

Browse files
authored
Merge pull request #7181 from jfinkels/sort-mem-percent
sort: support percent arguments to -S option
2 parents 5d6a04a + 4f83924 commit ceb0785

File tree

7 files changed

+116
-9
lines changed

7 files changed

+116
-9
lines changed

src/uu/dd/src/parseargs.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -517,9 +517,7 @@ fn parse_bytes_no_x(full: &str, s: &str) -> Result<u64, ParseError> {
517517
(None, None, None) => match parser.parse_u64(s) {
518518
Ok(n) => (n, 1),
519519
Err(ParseSizeError::SizeTooBig(_)) => (u64::MAX, 1),
520-
Err(ParseSizeError::InvalidSuffix(_) | ParseSizeError::ParseFailure(_)) => {
521-
return Err(ParseError::InvalidNumber(full.to_string()))
522-
}
520+
Err(_) => return Err(ParseError::InvalidNumber(full.to_string())),
523521
},
524522
(Some(i), None, None) => (parse_bytes_only(s, i)?, 1),
525523
(None, Some(i), None) => (parse_bytes_only(s, i)?, 2),

src/uu/df/src/df.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ impl Options {
189189
.to_string(),
190190
),
191191
ParseSizeError::ParseFailure(s) => OptionsError::InvalidBlockSize(s),
192+
ParseSizeError::PhysicalMem(s) => OptionsError::InvalidBlockSize(s),
192193
})?,
193194
header_mode: {
194195
if matches.get_flag(OPT_HUMAN_READABLE_BINARY)

src/uu/du/src/du.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1120,7 +1120,9 @@ fn format_error_message(error: &ParseSizeError, s: &str, option: &str) -> String
11201120
ParseSizeError::InvalidSuffix(_) => {
11211121
format!("invalid suffix in --{} argument {}", option, s.quote())
11221122
}
1123-
ParseSizeError::ParseFailure(_) => format!("invalid --{} argument {}", option, s.quote()),
1123+
ParseSizeError::ParseFailure(_) | ParseSizeError::PhysicalMem(_) => {
1124+
format!("invalid --{} argument {}", option, s.quote())
1125+
}
11241126
ParseSizeError::SizeTooBig(_) => format!("--{} argument {} too large", option, s.quote()),
11251127
}
11261128
}

src/uu/od/src/od.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -626,7 +626,9 @@ fn format_error_message(error: &ParseSizeError, s: &str, option: &str) -> String
626626
ParseSizeError::InvalidSuffix(_) => {
627627
format!("invalid suffix in --{} argument {}", option, s.quote())
628628
}
629-
ParseSizeError::ParseFailure(_) => format!("invalid --{} argument {}", option, s.quote()),
629+
ParseSizeError::ParseFailure(_) | ParseSizeError::PhysicalMem(_) => {
630+
format!("invalid --{} argument {}", option, s.quote())
631+
}
630632
ParseSizeError::SizeTooBig(_) => format!("--{} argument {} too large", option, s.quote()),
631633
}
632634
}

src/uu/sort/src/sort.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ impl GlobalSettings {
289289
// GNU sort (8.32) invalid: b, B, 1B, p, e, z, y
290290
let size = Parser::default()
291291
.with_allow_list(&[
292-
"b", "k", "K", "m", "M", "g", "G", "t", "T", "P", "E", "Z", "Y", "R", "Q",
292+
"b", "k", "K", "m", "M", "g", "G", "t", "T", "P", "E", "Z", "Y", "R", "Q", "%",
293293
])
294294
.with_default_unit("K")
295295
.with_b_byte_count(true)
@@ -1855,7 +1855,9 @@ fn format_error_message(error: &ParseSizeError, s: &str, option: &str) -> String
18551855
ParseSizeError::InvalidSuffix(_) => {
18561856
format!("invalid suffix in --{} argument {}", option, s.quote())
18571857
}
1858-
ParseSizeError::ParseFailure(_) => format!("invalid --{} argument {}", option, s.quote()),
1858+
ParseSizeError::ParseFailure(_) | ParseSizeError::PhysicalMem(_) => {
1859+
format!("invalid --{} argument {}", option, s.quote())
1860+
}
18591861
ParseSizeError::SizeTooBig(_) => format!("--{} argument {} too large", option, s.quote()),
18601862
}
18611863
}

src/uucore/src/lib/parser/parse_size.rs

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,70 @@
88
99
use std::error::Error;
1010
use std::fmt;
11-
use std::num::IntErrorKind;
11+
#[cfg(target_os = "linux")]
12+
use std::io::BufRead;
13+
use std::num::{IntErrorKind, ParseIntError};
1214

1315
use crate::display::Quotable;
1416

17+
/// Error arising from trying to compute system memory.
18+
enum SystemError {
19+
IOError,
20+
ParseError,
21+
NotFound,
22+
}
23+
24+
impl From<std::io::Error> for SystemError {
25+
fn from(_: std::io::Error) -> Self {
26+
Self::IOError
27+
}
28+
}
29+
30+
impl From<ParseIntError> for SystemError {
31+
fn from(_: ParseIntError) -> Self {
32+
Self::ParseError
33+
}
34+
}
35+
36+
/// Get the total number of bytes of physical memory.
37+
///
38+
/// The information is read from the `/proc/meminfo` file.
39+
///
40+
/// # Errors
41+
///
42+
/// If there is a problem reading the file or finding the appropriate
43+
/// entry in the file.
44+
#[cfg(target_os = "linux")]
45+
fn total_physical_memory() -> Result<u128, SystemError> {
46+
// On Linux, the `/proc/meminfo` file has a table with information
47+
// about memory usage. For example,
48+
//
49+
// MemTotal: 7811500 kB
50+
// MemFree: 1487876 kB
51+
// MemAvailable: 3857232 kB
52+
// ...
53+
//
54+
// We just need to extract the number of `MemTotal`
55+
let table = std::fs::read("/proc/meminfo")?;
56+
for line in table.lines() {
57+
let line = line?;
58+
if line.starts_with("MemTotal:") && line.ends_with("kB") {
59+
let num_kilobytes: u128 = line[9..line.len() - 2].trim().parse()?;
60+
let num_bytes = 1024 * num_kilobytes;
61+
return Ok(num_bytes);
62+
}
63+
}
64+
Err(SystemError::NotFound)
65+
}
66+
67+
/// Get the total number of bytes of physical memory.
68+
///
69+
/// TODO Implement this for non-Linux systems.
70+
#[cfg(not(target_os = "linux"))]
71+
fn total_physical_memory() -> Result<u128, SystemError> {
72+
Err(SystemError::NotFound)
73+
}
74+
1575
/// Parser for sizes in SI or IEC units (multiples of 1000 or 1024 bytes).
1676
///
1777
/// The [`Parser::parse`] function performs the parse.
@@ -133,6 +193,16 @@ impl<'parser> Parser<'parser> {
133193
}
134194
}
135195

196+
// Special case: for percentage, just compute the given fraction
197+
// of the total physical memory on the machine, if possible.
198+
if unit == "%" {
199+
let number: u128 = Self::parse_number(&numeric_string, 10, size)?;
200+
return match total_physical_memory() {
201+
Ok(total) => Ok((number / 100) * total),
202+
Err(_) => Err(ParseSizeError::PhysicalMem(size.to_string())),
203+
};
204+
}
205+
136206
// Compute the factor the unit represents.
137207
// empty string means the factor is 1.
138208
//
@@ -320,6 +390,9 @@ pub enum ParseSizeError {
320390

321391
/// Overflow
322392
SizeTooBig(String),
393+
394+
/// Could not determine total physical memory size.
395+
PhysicalMem(String),
323396
}
324397

325398
impl Error for ParseSizeError {
@@ -328,14 +401,18 @@ impl Error for ParseSizeError {
328401
Self::InvalidSuffix(ref s) => s,
329402
Self::ParseFailure(ref s) => s,
330403
Self::SizeTooBig(ref s) => s,
404+
Self::PhysicalMem(ref s) => s,
331405
}
332406
}
333407
}
334408

335409
impl fmt::Display for ParseSizeError {
336410
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
337411
let s = match self {
338-
Self::InvalidSuffix(s) | Self::ParseFailure(s) | Self::SizeTooBig(s) => s,
412+
Self::InvalidSuffix(s)
413+
| Self::ParseFailure(s)
414+
| Self::SizeTooBig(s)
415+
| Self::PhysicalMem(s) => s,
339416
};
340417
write!(f, "{s}")
341418
}
@@ -674,4 +751,16 @@ mod tests {
674751
assert_eq!(Ok(94722), parse_size_u64("0x17202"));
675752
assert_eq!(Ok(44251 * 1024), parse_size_u128("0xACDBK"));
676753
}
754+
755+
#[test]
756+
#[cfg(target_os = "linux")]
757+
fn parse_percent() {
758+
assert!(parse_size_u64("0%").is_ok());
759+
assert!(parse_size_u64("50%").is_ok());
760+
assert!(parse_size_u64("100%").is_ok());
761+
assert!(parse_size_u64("100000%").is_ok());
762+
assert!(parse_size_u64("-1%").is_err());
763+
assert!(parse_size_u64("1.0%").is_err());
764+
assert!(parse_size_u64("0x1%").is_err());
765+
}
677766
}

tests/by-util/test_sort.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ fn test_helper(file_name: &str, possible_args: &[&str]) {
2929

3030
#[test]
3131
fn test_buffer_sizes() {
32+
#[cfg(target_os = "linux")]
33+
let buffer_sizes = ["0", "50K", "50k", "1M", "100M", "0%", "10%"];
34+
// TODO Percentage sizes are not yet supported beyond Linux.
35+
#[cfg(not(target_os = "linux"))]
3236
let buffer_sizes = ["0", "50K", "50k", "1M", "100M"];
3337
for buffer_size in &buffer_sizes {
3438
TestScenario::new(util_name!())
@@ -73,6 +77,15 @@ fn test_invalid_buffer_size() {
7377
.code_is(2)
7478
.stderr_only("sort: invalid suffix in --buffer-size argument '100f'\n");
7579

80+
// TODO Percentage sizes are not yet supported beyond Linux.
81+
#[cfg(target_os = "linux")]
82+
new_ucmd!()
83+
.arg("-S")
84+
.arg("0x123%")
85+
.fails()
86+
.code_is(2)
87+
.stderr_only("sort: invalid --buffer-size argument '0x123%'\n");
88+
7689
new_ucmd!()
7790
.arg("-n")
7891
.arg("-S")

0 commit comments

Comments
 (0)