#![deny(unsafe_code)]
use std::fs;
use std::io;
use std::path::Path;
#[cfg_attr(docsrs, doc(cfg(any(target_os = "linux", target_os = "android"))))]
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct SysLimits {
pub file_max: Option<u64>,
pub file_nr: Option<FileNr>,
pub nr_open: Option<u64>,
}
#[cfg_attr(docsrs, doc(cfg(any(target_os = "linux", target_os = "android"))))]
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct FileNr {
pub allocated: u64,
pub free: u64,
pub maximum: u64,
}
impl SysLimits {
pub fn read() -> io::Result<Self> {
let file_max = read_u64_from_file("/proc/sys/fs/file-max").ok();
let file_nr = read_file_nr("/proc/sys/fs/file-nr").ok();
let nr_open = read_u64_from_file("/proc/sys/fs/nr_open").ok();
Ok(Self {
file_max,
file_nr,
nr_open,
})
}
pub fn set_file_max(value: u64) -> io::Result<()> {
write_u64_to_file("/proc/sys/fs/file-max", value)
}
pub fn set_nr_open(value: u64) -> io::Result<()> {
write_u64_to_file("/proc/sys/fs/nr_open", value)
}
}
fn read_u64_from_file(path: impl AsRef<Path>) -> io::Result<u64> {
let content = fs::read_to_string(path)?;
content
.trim()
.parse::<u64>()
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
}
fn read_file_nr(path: impl AsRef<Path>) -> io::Result<FileNr> {
let content = fs::read_to_string(path)?;
let parts: Vec<&str> = content.split_whitespace().collect();
if parts.len() != 3 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Expected 3 values in file-nr, got {}", parts.len()),
));
}
let allocated = parts[0]
.parse::<u64>()
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
let free = parts[1]
.parse::<u64>()
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
let maximum = parts[2]
.parse::<u64>()
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
Ok(FileNr {
allocated,
free,
maximum,
})
}
fn write_u64_to_file(path: impl AsRef<Path>, value: u64) -> io::Result<()> {
fs::write(path, value.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_read_system_limits() {
let result = SysLimits::read();
if cfg!(any(target_os = "linux", target_os = "android")) {
let limits = result.unwrap();
assert!(
limits.file_max.is_some() || limits.file_nr.is_some() || limits.nr_open.is_some(),
"At least one limit should be readable"
);
if let Some(file_nr) = limits.file_nr {
if let Some(file_max) = limits.file_max {
assert_eq!(file_nr.maximum, file_max);
}
}
}
}
#[test]
fn test_file_nr_parsing() {
use std::io::Write;
let temp_dir = std::env::temp_dir();
let test_file = temp_dir.join(format!(
"test_file_nr_{:?}.txt",
std::thread::current().id()
));
let mut file = fs::File::create(&test_file).unwrap();
write!(file, "2208\t0\t9223372036854775807").unwrap();
file.flush().unwrap();
drop(file);
let result = read_file_nr(&test_file).unwrap();
assert_eq!(result.allocated, 2208);
assert_eq!(result.free, 0);
assert_eq!(result.maximum, 9_223_372_036_854_775_807);
fs::remove_file(&test_file).ok();
}
#[test]
fn test_u64_parsing() {
use std::io::Write;
let temp_dir = std::env::temp_dir();
let test_file = temp_dir.join(format!(
"test_u64_{:?}.txt",
std::thread::current().id()
));
let mut file = fs::File::create(&test_file).unwrap();
writeln!(file, "1048576").unwrap();
file.flush().unwrap();
drop(file);
let result = read_u64_from_file(&test_file).unwrap();
assert_eq!(result, 1_048_576);
fs::remove_file(&test_file).ok();
}
}