Skip to content

Commit 23202fd

Browse files
committed
refactor: Parse HugeTLB size without Regex
This removes the only place the regex crate is used, which could lead to significant binary size savings in projects that don't use the regex craate.
1 parent eb3e37a commit 23202fd

2 files changed

Lines changed: 89 additions & 25 deletions

File tree

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ readme = "README.md"
1313

1414
[dependencies]
1515
log = "0.4"
16-
regex = "1.1"
1716
nix = { version = "0.25.0", default-features = false, features = ["event", "fs", "process"] }
1817
libc = "0.2"
1918
serde = { version = "1.0", features = ["derive"], optional = true }

src/hugetlb.rs

Lines changed: 89 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,6 @@ impl HugeTlbController {
181181
}
182182

183183
pub const HUGEPAGESIZE_DIR: &str = "/sys/kernel/mm/hugepages";
184-
use regex::Regex;
185184
use std::collections::HashMap;
186185
use std::fs;
187186

@@ -263,37 +262,46 @@ pub fn get_decimal_abbrs() -> Vec<String> {
263262
}
264263

265264
fn parse_size(s: &str, m: &HashMap<String, u128>) -> Result<u128> {
266-
let re = Regex::new(r"(?P<num>\d+)(?P<mul>[kKmMgGtTpP]?)[bB]?$");
265+
// Remove leading/trailing whitespace.
266+
let s = s.trim();
267267

268-
if re.is_err() {
268+
// Remove an optional trailing 'b' or 'B'
269+
let s = if let Some(stripped) = s.strip_suffix('b').or_else(|| s.strip_suffix('B')) {
270+
stripped
271+
} else {
272+
s
273+
};
274+
275+
// Ensure that the string is not empty after stripping.
276+
if s.is_empty() {
269277
return Err(Error::new(InvalidBytesSize));
270278
}
271-
let caps = re.unwrap().captures(s).unwrap();
272279

273-
let num = caps.name("num");
274-
let size: u128 = if let Some(num) = num {
275-
let n = num.as_str().trim().parse::<u128>();
276-
if n.is_err() {
277-
return Err(Error::new(InvalidBytesSize));
278-
}
279-
n.unwrap()
280-
} else {
280+
// The last character should be the multiplier letter.
281+
let last_char = s.chars().last().unwrap();
282+
if !"kKmMgGtTpP".contains(last_char) {
281283
return Err(Error::new(InvalidBytesSize));
282-
};
284+
}
283285

284-
let q = caps.name("mul");
285-
let mul: u128 = if let Some(q) = q {
286-
let t = m.get(q.as_str());
287-
if let Some(t) = t {
288-
*t
289-
} else {
290-
return Err(Error::new(InvalidBytesSize));
291-
}
292-
} else {
286+
// The numeric part is everything before the multiplier letter.
287+
let num_part = &s[..s.len() - last_char.len_utf8()];
288+
if num_part.trim().is_empty() {
293289
return Err(Error::new(InvalidBytesSize));
294-
};
290+
}
291+
292+
// Parse the numeric part into a u128.
293+
let number: u128 = num_part
294+
.trim()
295+
.parse()
296+
.map_err(|_| Error::new(InvalidBytesSize))?;
295297

296-
Ok(size * mul)
298+
// Look up the multiplier in the provided HashMap.
299+
let multiplier_key = last_char.to_string();
300+
let multiplier = m
301+
.get(&multiplier_key)
302+
.ok_or_else(|| Error::new(InvalidBytesSize))?;
303+
304+
Ok(number * multiplier)
297305
}
298306

299307
fn custom_size(mut size: f64, base: f64, m: &[String]) -> String {
@@ -305,3 +313,60 @@ fn custom_size(mut size: f64, base: f64, m: &[String]) -> String {
305313

306314
format!("{}{}", size, m[i].as_str())
307315
}
316+
317+
#[cfg(test)]
318+
mod tests {
319+
use super::*;
320+
321+
#[test]
322+
fn test_binary_size_valid() {
323+
let m = get_binary_size_map();
324+
// Valid inputs must include a multiplier letter.
325+
assert_eq!(parse_size("1k", &m).unwrap(), KiB);
326+
assert_eq!(parse_size("2m", &m).unwrap(), 2 * MiB);
327+
assert_eq!(parse_size("3g", &m).unwrap(), 3 * GiB);
328+
assert_eq!(parse_size("4t", &m).unwrap(), 4 * TiB);
329+
assert_eq!(parse_size("5p", &m).unwrap(), 5 * PiB);
330+
}
331+
332+
#[test]
333+
fn test_decimal_size_valid() {
334+
let m = get_decimal_size_map();
335+
assert_eq!(parse_size("1k", &m).unwrap(), KB);
336+
assert_eq!(parse_size("2m", &m).unwrap(), 2 * MB);
337+
assert_eq!(parse_size("3g", &m).unwrap(), 3 * GB);
338+
assert_eq!(parse_size("4t", &m).unwrap(), 4 * TB);
339+
assert_eq!(parse_size("5p", &m).unwrap(), 5 * PB);
340+
}
341+
342+
#[test]
343+
fn test_trailing_b_suffix() {
344+
let m = get_binary_size_map();
345+
// Trailing 'b' or 'B' should be accepted.
346+
assert_eq!(parse_size("1kb", &m).unwrap(), KiB);
347+
assert_eq!(parse_size("2mB", &m).unwrap(), 2 * MiB);
348+
}
349+
350+
#[test]
351+
fn test_invalid_inputs() {
352+
let m = get_binary_size_map();
353+
// Missing multiplier letter results in error.
354+
assert!(parse_size("1", &m).is_err());
355+
// Invalid multiplier letter.
356+
assert!(parse_size("10x", &m).is_err());
357+
// Non-numeric input.
358+
assert!(parse_size("abc", &m).is_err());
359+
// Only multiplier letter with no number.
360+
assert!(parse_size("k", &m).is_err());
361+
// Number with an invalid trailing character.
362+
assert!(parse_size("123z", &m).is_err());
363+
}
364+
365+
#[test]
366+
fn test_uppercase_multiplier_fails() {
367+
let m = get_binary_size_map();
368+
// Although the regex matches uppercase letters, the provided map only contains lowercase keys.
369+
// Therefore, "1K" does not match any key and should produce an error.
370+
assert!(parse_size("1K", &m).is_err());
371+
}
372+
}

0 commit comments

Comments
 (0)