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