1#![deny(unsafe_code)]
2
3use std::fs;
4use std::io::{self, BufRead};
5use std::num::ParseIntError;
6use std::path::Path;
7
8use libc::pid_t;
9
10#[derive(Debug, Clone, Default)]
15#[non_exhaustive]
16pub struct ProcLimits {
17 pub max_cpu_time: Option<ProcLimit>,
19 pub max_file_size: Option<ProcLimit>,
21 pub max_data_size: Option<ProcLimit>,
23 pub max_stack_size: Option<ProcLimit>,
25 pub max_core_file_size: Option<ProcLimit>,
27 pub max_resident_set: Option<ProcLimit>,
29 pub max_processes: Option<ProcLimit>,
31 pub max_open_files: Option<ProcLimit>,
33 pub max_locked_memory: Option<ProcLimit>,
35 pub max_address_space: Option<ProcLimit>,
37 pub max_file_locks: Option<ProcLimit>,
39 pub max_pending_signals: Option<ProcLimit>,
41 pub max_msgqueue_size: Option<ProcLimit>,
43 pub max_nice_priority: Option<ProcLimit>,
45 pub max_realtime_priority: Option<ProcLimit>,
47 pub max_realtime_timeout: Option<ProcLimit>,
49}
50
51#[derive(Debug, Clone, Default, PartialEq, Eq)]
53pub struct ProcLimit {
54 pub soft_limit: Option<u64>,
56 pub hard_limit: Option<u64>,
58}
59
60impl ProcLimits {
61 pub fn read_self() -> io::Result<Self> {
69 Self::read_proc_fs("/proc/self/limits")
70 }
71
72 pub fn read_process(pid: pid_t) -> io::Result<Self> {
82 if pid < 0 {
83 return Err(io::Error::new(
84 io::ErrorKind::InvalidInput,
85 "ProcLimits: pid must be non-negative",
86 ));
87 }
88 Self::read_proc_fs(format!("/proc/{}/limits", pid))
89 }
90
91 fn read_proc_fs(limits_path: impl AsRef<Path>) -> io::Result<Self> {
92 fn parse_head(head: &str) -> Option<(usize, usize, usize)> {
93 let s_idx = head.find('S')?;
94 let h_idx = head[s_idx..].find('H')?;
95 let u_idx = head[s_idx + h_idx..].find('U')?;
96 Some((s_idx, h_idx, u_idx))
97 }
98
99 fn parse_limit_number(s: &str) -> Result<Option<u64>, ParseIntError> {
100 match s {
101 "unlimited" => Ok(None),
102 _ => match s.parse::<u64>() {
103 Ok(n) => Ok(Some(n)),
104 Err(e) => Err(e),
105 },
106 }
107 }
108
109 fn error_missing_table_head() -> io::Error {
110 io::Error::new(io::ErrorKind::Other, "ProcLimits: missing table head")
111 }
112
113 fn error_invalid_table_head() -> io::Error {
114 io::Error::new(io::ErrorKind::Other, "ProcLimits: invalid table head")
115 }
116
117 fn error_invalid_limit_number(e: ParseIntError) -> io::Error {
118 let ans = io::Error::new(
119 io::ErrorKind::Other,
120 format!("ProcLimits: invalid limit number: {}", e),
121 );
122 drop(e);
123 ans
124 }
125
126 fn error_duplicate_limit_field() -> io::Error {
127 io::Error::new(io::ErrorKind::Other, "ProcLimits: duplicate limit field")
128 }
129
130 fn error_unknown_limit_field(s: &str) -> io::Error {
131 io::Error::new(
132 io::ErrorKind::Other,
133 format!("ProcLimits: unknown limit field: {:?}", s),
134 )
135 }
136
137 let reader = io::BufReader::new(fs::File::open(limits_path)?);
138 let mut lines = reader.lines();
139
140 let head = lines.next().ok_or_else(error_missing_table_head)??;
141
142 let (name_len, soft_len, hard_len) =
143 parse_head(&head).ok_or_else(error_invalid_table_head)?;
144
145 let mut ans = Self::default();
146
147 let sorted_table: [(&str, &mut Option<ProcLimit>); 16] = [
148 ("max address space", &mut ans.max_address_space),
149 ("max core file size", &mut ans.max_core_file_size),
150 ("max cpu time", &mut ans.max_cpu_time),
151 ("max data size", &mut ans.max_data_size),
152 ("max file locks", &mut ans.max_file_locks),
153 ("max file size", &mut ans.max_file_size),
154 ("max locked memory", &mut ans.max_locked_memory),
155 ("max msgqueue size", &mut ans.max_msgqueue_size),
156 ("max nice priority", &mut ans.max_nice_priority),
157 ("max open files", &mut ans.max_open_files),
158 ("max pending signals", &mut ans.max_pending_signals),
159 ("max processes", &mut ans.max_processes),
160 ("max realtime priority", &mut ans.max_realtime_priority),
161 ("max realtime timeout", &mut ans.max_realtime_timeout),
162 ("max resident set", &mut ans.max_resident_set),
163 ("max stack size", &mut ans.max_stack_size),
164 ];
165
166 for line in lines {
167 let line = line?;
168
169 let (name, line) = line.split_at(name_len);
170 let (soft, line) = line.split_at(soft_len);
171 let (hard, _) = line.split_at(hard_len);
172
173 let name = name.trim().to_lowercase();
174 let soft_limit = parse_limit_number(soft.trim()).map_err(error_invalid_limit_number)?;
175 let hard_limit = parse_limit_number(hard.trim()).map_err(error_invalid_limit_number)?;
176 let limit = ProcLimit {
177 soft_limit,
178 hard_limit,
179 };
180
181 match sorted_table.binary_search_by_key(&name.as_str(), |&(s, _)| s) {
182 Ok(idx) => {
183 let field = &mut *sorted_table[idx].1;
184 if field.is_some() {
185 return Err(error_duplicate_limit_field());
186 }
187 *field = Some(limit)
188 }
189 Err(_) => return Err(error_unknown_limit_field(&name)),
190 }
191 }
192
193 Ok(ans)
194 }
195}