Skip to content

Commit 88dbe19

Browse files
committed
Remove procfs dependency and fix percent parsing bugs in parse_size
1 parent dc982b1 commit 88dbe19

File tree

5 files changed

+88
-161
lines changed

5 files changed

+88
-161
lines changed

Cargo.lock

Lines changed: 0 additions & 129 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,6 @@ parse_datetime = "0.13.0"
364364
phf = "0.13.1"
365365
phf_codegen = "0.13.1"
366366
platform-info = "2.0.3"
367-
procfs = "0.18"
368367
rand = { version = "0.9.0", features = ["small_rng"] }
369368
rand_chacha = { version = "0.9.0" }
370369
rayon = "1.10"

src/uucore/Cargo.toml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,6 @@ xattr = { workspace = true, optional = true }
108108
[dev-dependencies]
109109
tempfile = { workspace = true }
110110

111-
[target.'cfg(target_os = "linux")'.dependencies]
112-
procfs = { workspace = true, optional = true }
113-
114111
[target.'cfg(target_os = "windows")'.dependencies]
115112
wild = "2.2.1"
116113
winapi-util = { workspace = true, optional = true }
@@ -166,7 +163,7 @@ mode = ["libc"]
166163
perms = ["entries", "libc", "walkdir"]
167164
buf-copy = []
168165
parser-num = ["extendedbigdecimal", "num-traits"]
169-
parser-size = ["parser-num", "procfs"]
166+
parser-size = ["parser-num"]
170167
parser-glob = ["glob"]
171168
parser = ["parser-num", "parser-size", "parser-glob"]
172169
pipes = []

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

Lines changed: 59 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,9 @@
88
99
use std::error::Error;
1010
use std::fmt;
11-
use std::num::{IntErrorKind, ParseIntError};
11+
use std::num::IntErrorKind;
1212

1313
use crate::display::Quotable;
14-
#[cfg(target_os = "linux")]
15-
use procfs::{Current, Meminfo};
1614

1715
/// Error arising from trying to compute system memory.
1816
enum SystemError {
@@ -28,10 +26,48 @@ impl From<std::io::Error> for SystemError {
2826
}
2927
}
3028

31-
impl From<ParseIntError> for SystemError {
32-
fn from(_: ParseIntError) -> Self {
33-
Self::ParseError
29+
#[cfg(target_os = "linux")]
30+
fn parse_meminfo_kb(line: &str, key: &str) -> Option<u128> {
31+
line.strip_prefix(key)?
32+
.strip_prefix(':')?
33+
.trim()
34+
.trim_end_matches(" kB")
35+
.trim()
36+
.parse::<u128>()
37+
.ok()
38+
.map(|kb| kb.saturating_mul(1024))
39+
}
40+
41+
#[cfg(target_os = "linux")]
42+
fn read_meminfo() -> Result<(u128, u128), SystemError> {
43+
let contents = std::fs::read_to_string("/proc/meminfo")?;
44+
let mut total = None;
45+
let mut available = None;
46+
let mut free = 0u128;
47+
let mut buffers = 0u128;
48+
let mut cached = 0u128;
49+
for line in contents.lines() {
50+
if let Some(v) = parse_meminfo_kb(line, "MemTotal") {
51+
total = Some(v);
52+
} else if let Some(v) = parse_meminfo_kb(line, "MemAvailable") {
53+
available = Some(v);
54+
} else if let Some(v) = parse_meminfo_kb(line, "MemFree") {
55+
free = v;
56+
} else if let Some(v) = parse_meminfo_kb(line, "Buffers") {
57+
buffers = v;
58+
} else if let Some(v) = parse_meminfo_kb(line, "Cached") {
59+
cached = v;
60+
}
3461
}
62+
let total = total.ok_or(SystemError::ParseError)?;
63+
let available = available
64+
.filter(|&v| v > 0)
65+
.or_else(|| {
66+
let fallback = free.saturating_add(buffers).saturating_add(cached);
67+
(fallback > 0).then_some(fallback)
68+
})
69+
.unwrap_or(total);
70+
Ok((total, available))
3571
}
3672

3773
/// Get the total number of bytes of physical memory.
@@ -44,31 +80,13 @@ impl From<ParseIntError> for SystemError {
4480
/// entry in the file.
4581
#[cfg(target_os = "linux")]
4682
fn total_physical_memory() -> Result<u128, SystemError> {
47-
let info = Meminfo::current().map_err(|_| SystemError::IOError)?;
48-
Ok((info.mem_total as u128).saturating_mul(1024))
83+
read_meminfo().map(|(total, _)| total)
4984
}
5085

5186
/// Return the number of bytes of memory that appear to be currently available.
5287
#[cfg(target_os = "linux")]
5388
pub fn available_memory_bytes() -> Option<u128> {
54-
let info = Meminfo::current().ok()?;
55-
56-
if let Some(available_kib) = info.mem_available {
57-
let available_bytes = (available_kib as u128).saturating_mul(1024);
58-
if available_bytes > 0 {
59-
return Some(available_bytes);
60-
}
61-
}
62-
63-
let fallback_kib = (info.mem_free as u128)
64-
.saturating_add(info.buffers as u128)
65-
.saturating_add(info.cached as u128);
66-
67-
if fallback_kib > 0 {
68-
Some(fallback_kib.saturating_mul(1024))
69-
} else {
70-
total_physical_memory().ok()
71-
}
89+
read_meminfo().ok().map(|(_, available)| available)
7290
}
7391

7492
/// Return `None` when the platform does not expose Linux-like `/proc/meminfo`.
@@ -219,7 +237,7 @@ impl<'parser> Parser<'parser> {
219237
if unit == "%" {
220238
let number: u128 = Self::parse_number(&numeric_string, 10, size)?;
221239
return match total_physical_memory() {
222-
Ok(total) => Ok((number / 100) * total),
240+
Ok(total) => Ok(total * number / 100),
223241
Err(_) => Err(ParseSizeError::PhysicalMem(size.to_string())),
224242
};
225243
}
@@ -815,4 +833,18 @@ mod tests {
815833
assert!(parse_size_u64("1.0%").is_err());
816834
assert!(parse_size_u64("0x1%").is_err());
817835
}
836+
837+
#[test]
838+
#[cfg(target_os = "linux")]
839+
fn parse_percent_values() {
840+
let total = total_physical_memory().ok().unwrap();
841+
842+
// 100% should equal total physical memory, not 1024x too large
843+
assert_eq!(Ok(total), parse_size_u128("100%"));
844+
845+
// 50% should be half of total, not 0 from integer division truncation
846+
assert_eq!(Ok(total / 2), parse_size_u128("50%"));
847+
848+
assert_eq!(Ok(0), parse_size_u128("0%"));
849+
}
818850
}

tests/by-util/test_sort.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,34 @@ fn test_helper(file_name: &str, possible_args: &[&str]) {
3333
}
3434
}
3535

36+
#[test]
37+
#[cfg(target_os = "linux")]
38+
fn test_percentage_buffer_no_spill() {
39+
let (at, mut ucmd) = at_and_ucmd!();
40+
at.mkdir("tmp_dir");
41+
at.write(
42+
"input.txt",
43+
&(0..100_000)
44+
.rev()
45+
.map(|i| format!("{i}\n"))
46+
.collect::<String>(),
47+
);
48+
ucmd.args(&[
49+
"input.txt",
50+
"-n",
51+
"-S",
52+
"50%",
53+
"--temporary-directory=tmp_dir",
54+
])
55+
.succeeds();
56+
assert!(
57+
std::fs::read_dir(at.plus("tmp_dir"))
58+
.unwrap()
59+
.next()
60+
.is_none()
61+
);
62+
}
63+
3664
#[test]
3765
fn test_buffer_sizes() {
3866
#[cfg(target_os = "linux")]

0 commit comments

Comments
 (0)