use std::io::BufRead;
use std::io::{self};
use std::str::FromStr;
use nix::sys::resource::Resource;
use super::FromBufRead;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LimitValue {
Unlimited,
Value(u64),
}
impl FromStr for LimitValue {
type Err = std::num::ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.eq_ignore_ascii_case("unlimited") {
Ok(LimitValue::Unlimited)
} else {
s.parse::<u64>().map(LimitValue::Value)
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Limit {
pub soft: LimitValue,
pub hard: LimitValue,
pub unit: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Limits(Vec<(Resource, Limit)>);
impl Limits {
pub fn iter(&self) -> impl Iterator<Item = (Resource, &Limit)> {
self.0.iter().map(|(r, l)| (*r, l))
}
pub fn get(&self, resource: Resource) -> Option<&Limit> {
self.0
.iter()
.find_map(|(r, l)| if *r == resource { Some(l) } else { None })
}
}
const KERNEL_LIMITS: &[(&str, Resource)] = &[
("Max cpu time", Resource::RLIMIT_CPU),
("Max file size", Resource::RLIMIT_FSIZE),
("Max data size", Resource::RLIMIT_DATA),
("Max stack size", Resource::RLIMIT_STACK),
("Max core file size", Resource::RLIMIT_CORE),
("Max resident set", Resource::RLIMIT_RSS),
("Max processes", Resource::RLIMIT_NPROC),
("Max open files", Resource::RLIMIT_NOFILE),
("Max locked memory", Resource::RLIMIT_MEMLOCK),
("Max address space", Resource::RLIMIT_AS),
("Max file locks", Resource::RLIMIT_LOCKS),
("Max pending signals", Resource::RLIMIT_SIGPENDING),
("Max msgqueue size", Resource::RLIMIT_MSGQUEUE),
("Max nice priority", Resource::RLIMIT_NICE),
("Max realtime priority", Resource::RLIMIT_RTPRIO),
("Max realtime timeout", Resource::RLIMIT_RTTIME),
];
fn kernel_name_to_resource(name: &str) -> Option<Resource> {
KERNEL_LIMITS
.iter()
.find_map(|&(n, r)| if n == name { Some(r) } else { None })
}
fn parse_limit(soft_str: &str, hard_str: &str, unit: Option<String>) -> io::Result<Limit> {
let soft = soft_str.parse::<LimitValue>().map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("invalid soft limit '{soft_str}': {e}"),
)
})?;
let hard = hard_str.parse::<LimitValue>().map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("invalid hard limit '{hard_str}': {e}"),
)
})?;
Ok(Limit { soft, hard, unit })
}
impl FromBufRead for Limits {
fn from_buf_read(reader: impl BufRead) -> io::Result<Self> {
let mut lines = reader.lines();
let header = lines
.next()
.ok_or_else(|| io::Error::new(io::ErrorKind::UnexpectedEof, "empty limits file"))??;
let hdr = header.as_bytes();
let cols: Vec<usize> = (2..header.len())
.filter(|&i| hdr[i] != b' ' && hdr[i - 1] == b' ' && hdr[i - 2] == b' ')
.collect();
if cols.len() < 3 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"cannot detect column boundaries in limits header",
));
}
let (soft_col, hard_col, units_col) = (cols[0], cols[1], cols[2]);
let mut limits = Vec::new();
for line_result in lines {
let line = line_result?;
if line.is_empty() {
continue;
}
if line.len() < hard_col {
eprintln!("warning: skipping malformed limits line: {line}");
continue;
}
let name = line[..soft_col].trim();
let soft_str = line[soft_col..hard_col].trim();
let hard_str = line[hard_col..units_col.min(line.len())].trim();
let unit_str = line.get(units_col..).map(str::trim).unwrap_or("");
let unit = if unit_str.is_empty() {
None
} else {
Some(unit_str.to_string())
};
match kernel_name_to_resource(name) {
Some(resource) => match parse_limit(soft_str, hard_str, unit) {
Ok(limit) => limits.push((resource, limit)),
Err(e) => {
eprintln!("warning: failed to parse limit values for '{name}': {e}");
}
},
None => {
eprintln!("warning: unknown kernel limit name: '{name}'");
}
}
}
if limits.len() < KERNEL_LIMITS.len() {
eprintln!(
"warning: parsed only {} of {} expected limits",
limits.len(),
KERNEL_LIMITS.len()
);
}
Ok(Limits(limits))
}
}