peek_proc_reader/
limits.rs1use crate::error::{io_to_error, Result};
7use std::path::PathBuf;
8
9#[derive(Debug, Clone, Default)]
11pub struct Limits {
12 pub max_open_files_soft: Option<u64>,
14 pub max_open_files_hard: Option<u64>,
16}
17
18#[cfg(target_os = "linux")]
20pub fn read_limits(pid: i32) -> Result<Limits> {
21 let path = PathBuf::from(format!("/proc/{}/limits", pid));
22 let raw = std::fs::read_to_string(&path).map_err(|e| io_to_error(path.clone(), e, pid))?;
23
24 let mut out = Limits::default();
25
26 for line in raw.lines() {
27 if !line.starts_with("Max open files") {
28 continue;
29 }
30
31 let mut parts = line.split_whitespace();
34
35 let _ = parts.next();
37 let _ = parts.next();
38 let _ = parts.next();
39
40 let soft = parts.next();
41 let hard = parts.next();
42
43 out.max_open_files_soft = parse_limit_field(soft);
44 out.max_open_files_hard = parse_limit_field(hard);
45
46 break;
47 }
48
49 Ok(out)
50}
51
52#[cfg(not(target_os = "linux"))]
54pub fn read_limits(_pid: i32) -> Result<Limits> {
55 Ok(Limits::default())
56}
57
58fn parse_limit_field(field: Option<&str>) -> Option<u64> {
59 let v = field?;
60 if v == "unlimited" {
61 return None;
62 }
63 v.parse::<u64>().ok()
64}
65
66#[cfg(test)]
67mod tests {
68 use super::parse_limit_field;
69 use proptest::prelude::*;
70
71 #[test]
72 fn parses_numeric_limits() {
73 assert_eq!(parse_limit_field(Some("1024")), Some(1024));
74 assert_eq!(parse_limit_field(Some("0")), Some(0));
75 }
76
77 #[test]
78 fn treats_unlimited_as_none() {
79 assert_eq!(parse_limit_field(Some("unlimited")), None);
80 }
81
82 #[test]
83 fn handles_missing_field() {
84 assert_eq!(parse_limit_field(None), None);
85 }
86
87 proptest! {
88 #[test]
89 fn parse_limit_field_never_panics_for_arbitrary_strings(s in ".*") {
90 let _ = parse_limit_field(Some(&s));
92 }
93 }
94}