rlimit/
proc_limits.rs

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/// A process's resource limits. It is parsed from the **proc** filesystem.
11///
12/// See <https://man7.org/linux/man-pages/man5/proc.5.html>.
13///
14#[cfg_attr(docsrs, doc(cfg(target_os = "linux")))]
15#[derive(Debug, Clone, Default)]
16#[non_exhaustive]
17pub struct ProcLimits {
18    /// Max cpu time. See also [Resource::CPU](struct.Resource.html#associatedconstant.CPU).
19    pub max_cpu_time: Option<ProcLimit>,
20    /// Max file size. See also [Resource::FSIZE](struct.Resource.html#associatedconstant.FSIZE).
21    pub max_file_size: Option<ProcLimit>,
22    /// Max data size. See also [Resource::DATA](struct.Resource.html#associatedconstant.DATA).
23    pub max_data_size: Option<ProcLimit>,
24    /// Max stack size. See also [Resource::STACK](struct.Resource.html#associatedconstant.STACK).
25    pub max_stack_size: Option<ProcLimit>,
26    /// Max core file size. See also [Resource::CORE](struct.Resource.html#associatedconstant.CORE).
27    pub max_core_file_size: Option<ProcLimit>,
28    /// Max resident set. See also [Resource::RSS](struct.Resource.html#associatedconstant.RSS).
29    pub max_resident_set: Option<ProcLimit>,
30    /// Max processes. See also [Resource::NPROC](struct.Resource.html#associatedconstant.NPROC).
31    pub max_processes: Option<ProcLimit>,
32    /// Max open files. See also [Resource::NOFILE](struct.Resource.html#associatedconstant.NOFILE).
33    pub max_open_files: Option<ProcLimit>,
34    /// Max locked memory. See also [Resource::MEMLOCK](struct.Resource.html#associatedconstant.MEMLOCK).
35    pub max_locked_memory: Option<ProcLimit>,
36    /// Max address space. See also [Resource::AS](struct.Resource.html#associatedconstant.AS).
37    pub max_address_space: Option<ProcLimit>,
38    /// Max file locks. See also [Resource::LOCKS](struct.Resource.html#associatedconstant.LOCKS).
39    pub max_file_locks: Option<ProcLimit>,
40    /// Max pending signals. See also [Resource::SIGPENDING](struct.Resource.html#associatedconstant.SIGPENDING).
41    pub max_pending_signals: Option<ProcLimit>,
42    /// Max msgqueue size. See also [Resource::MSGQUEUE](struct.Resource.html#associatedconstant.MSGQUEUE).
43    pub max_msgqueue_size: Option<ProcLimit>,
44    /// Max nice priority. See also [Resource::NICE](struct.Resource.html#associatedconstant.NICE).
45    pub max_nice_priority: Option<ProcLimit>,
46    /// Max realtime priority. See also [Resource::RTPRIO](struct.Resource.html#associatedconstant.RTPRIO).
47    pub max_realtime_priority: Option<ProcLimit>,
48    /// Max realtime timeout. See also [Resource::RTTIME](struct.Resource.html#associatedconstant.RTTIME).
49    pub max_realtime_timeout: Option<ProcLimit>,
50}
51
52/// A process's resource limit field.
53#[cfg_attr(docsrs, doc(cfg(target_os = "linux")))]
54#[derive(Debug, Clone, Default, PartialEq, Eq)]
55pub struct ProcLimit {
56    /// Soft limit. `None` indicates `unlimited`.
57    pub soft_limit: Option<u64>,
58    /// Hard limit. `None` indicates `unlimited`.
59    pub hard_limit: Option<u64>,
60}
61
62impl ProcLimits {
63    /// Reads the current process's resource limits from `/proc/self/limits`.
64    ///
65    /// # Errors
66    /// Returns an error if any IO operation failed.
67    ///
68    /// Returns an error if the file format is invalid.
69    ///
70    pub fn read_self() -> io::Result<Self> {
71        Self::read_proc_fs("/proc/self/limits")
72    }
73
74    /// Reads a process's resource limits from `/proc/[pid]/limits`.
75    ///
76    /// # Errors
77    /// Returns an error if `pid` is negative.
78    ///
79    /// Returns an error if any IO operation failed.
80    ///
81    /// Returns an error if the file format is invalid.
82    ///
83    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}