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