limits_rs/linux/
mod.rs

1/// A limit for a GNU/Linux specific limitable property.
2///
3/// Any given limit always contain a _soft_ and a _hard_ limit.
4///
5/// A soft or hard limited whose value is `None` here means there is no actual limit, i.e. the value
6/// found in `/proc/<pid>/limits` is `unlimited`.
7#[derive(Debug, Default, Eq, PartialEq)]
8pub struct Limit {
9    pub soft: Option<u32>,
10    pub hard: Option<u32>,
11}
12
13/// A structure containing all possible properties that can be limited by a GNU/Linux operating
14/// system.
15#[derive(Debug, Default, Eq, PartialEq)]
16pub struct Limits {
17    pub max_cpu_time: Limit,
18    pub max_file_size: Limit,
19    pub max_data_size: Limit,
20    pub max_stack_size: Limit,
21    pub max_core_file_size: Limit,
22    pub max_resident_set: Limit,
23    pub max_processes: Limit,
24    pub max_open_files: Limit,
25    pub max_locked_memory: Limit,
26    pub max_address_space: Limit,
27    pub max_file_locks: Limit,
28    pub max_pending_signals: Limit,
29    pub max_msgqueue_size: Limit,
30    pub max_nice_priority: Limit,
31    pub max_realtime_priority: Limit,
32    pub max_realtime_timeout: Limit,
33}
34
35impl Limits {
36    /// Set properties on a `Limit` structure, as read from strings.
37    ///
38    /// # Examples
39    ///
40    /// ```rust
41    /// use limits_rs::{Limit, Limits};
42    ///
43    /// // Create a new limits structure
44    /// let mut limits = Limits::default();
45    ///
46    /// // Trying to set a non-existing property should do nothing
47    /// limits.set_property_from_strings("Does_not_exist", "123", "456");
48    /// assert_eq!(limits, Limits::default());
49    ///
50    /// // Let's set a limit for a existing property and assert that the limit is actually stored in
51    /// // the structure
52    /// limits.set_property_from_strings("Max file locks", "123", "456");
53    /// assert_eq!(limits.max_file_locks, Limit { soft: Some(123), hard: Some(456) })
54    ///
55    /// ```
56    pub fn set_property_from_strings(&mut self, name: &str, soft_string: &str, hard_string: &str) {
57        use std::str::FromStr;
58
59        let lower_case = name.to_lowercase();
60
61        let soft = if soft_string == "unlimited" {
62            None
63        } else {
64            u32::from_str(soft_string).ok()
65        };
66
67        let hard = if hard_string == "unlimited" {
68            None
69        } else {
70            u32::from_str(hard_string).ok()
71        };
72
73        let new_limit = Limit { soft, hard };
74
75        match lower_case.as_str() {
76            "max cpu time" => self.max_cpu_time = new_limit,
77            "max file_size" => self.max_file_size = new_limit,
78            "max data size" => self.max_data_size = new_limit,
79            "max stack size" => self.max_stack_size = new_limit,
80            "max core file size" => self.max_core_file_size = new_limit,
81            "max resident set" => self.max_resident_set = new_limit,
82            "max processes" => self.max_processes = new_limit,
83            "max open files" => self.max_open_files = new_limit,
84            "max locked memory" => self.max_locked_memory = new_limit,
85            "max address space" => self.max_address_space = new_limit,
86            "max file locks" => self.max_file_locks = new_limit,
87            "max pending signals" => self.max_pending_signals = new_limit,
88            "max msgqueue size" => self.max_msgqueue_size = new_limit,
89            "max nice priority" => self.max_nice_priority = new_limit,
90            "max realtime priority" => self.max_realtime_priority = new_limit,
91            "max realtime timeout" => self.max_realtime_timeout = new_limit,
92            _ => (),
93        }
94    }
95}
96
97/// Get the limits for a specific process identifier.
98///
99/// Along `get_own_limits`, this method provides the core functionality of this crate.
100///
101/// # Examples
102///
103/// ```rust
104/// use limits_rs::get_pid_limits;
105///
106/// // Let's check what the CPU time hard limit is for process `1`.
107/// let limits = get_pid_limits(1).unwrap();
108/// let max_cpu_time_hard_limit = limits.max_cpu_time.hard;
109/// ```
110pub fn get_pid_limits(pid: u32) -> Result<Limits, crate::Error> {
111    // Rad the limits file for the process, and put all the lines into an iterator.
112    let file_path = format!("/proc/{}/limits", pid);
113    let file = std::fs::File::open(&file_path)
114        .map_err(|io_error| crate::Error::ProcFileNotFound(file_path, io_error))?;
115    let reader = std::io::BufReader::new(file);
116
117    get_limits_from_reader(reader)
118}
119
120/// Read limits from any type that implements the `std::io::BufRead` crate, such as
121/// `std::io::BufReader` or `std::io::Cursor`.
122fn get_limits_from_reader<T>(reader: T) -> Result<Limits, crate::Error>
123where
124    T: std::io::BufRead,
125{
126    let mut limits = Limits::default();
127    let mut lines = std::io::BufRead::lines(reader).filter_map(Result::ok);
128
129    // Skip first line, which always contains the table header.
130    lines.next();
131
132    for line in lines {
133        // Separate the name of the property from the rest of the table, which is padded to 27
134        // characters, i.e. the soft limits always start at character 27.
135        let (property, values) = line.split_at(26);
136        let property = property.trim();
137        let values: Vec<&str> = values.split_whitespace().collect();
138        limits.set_property_from_strings(property, values[0], values[1]);
139    }
140
141    Ok(limits)
142}
143
144#[cfg(test)]
145mod tests {
146    use crate::{Limit, Limits};
147
148    #[test]
149    fn test_own_limits_does_not_panic() {
150        crate::get_own_limits().unwrap();
151    }
152
153    #[test]
154    fn test_pid_limits_does_not_panic() {
155        crate::get_pid_limits(1).unwrap();
156    }
157
158    #[test]
159    fn test_proc_file_not_found() {
160        let error = format!("{:?}", super::get_pid_limits(std::u32::MAX).unwrap_err());
161        let expected_error = String::from(
162            r#"ProcFileNotFound("/proc/4294967295/limits", Os { code: 2, kind: NotFound, message: "No such file or directory" })"#,
163        );
164
165        assert_eq!(error, expected_error);
166    }
167
168    #[test]
169    fn test_from_empty_string() {
170        let reader = std::io::Cursor::new("");
171        let limits = super::get_limits_from_reader(reader).unwrap();
172
173        let expected_limits = Limits::default();
174
175        assert_eq!(limits, expected_limits);
176    }
177
178    #[test]
179    fn test_from_correct_string() {
180        let reader = std::io::Cursor::new(
181            r#"Limit                     Soft Limit           Hard Limit           Units
182Max cpu time              unlimited            unlimited            seconds
183Max file size             unlimited            unlimited            bytes
184Max data size             unlimited            unlimited            bytes
185Max stack size            8388608              unlimited            bytes
186Max core file size        unlimited            unlimited            bytes
187Max resident set          unlimited            unlimited            bytes
188Max processes             62935                62935                processes
189Max open files            1024                 524288               files
190Max locked memory         65536                65536                bytes
191Max address space         unlimited            unlimited            bytes
192Max file locks            unlimited            unlimited            locks
193Max pending signals       62935                62935                signals
194Max msgqueue size         819200               819200               bytes
195Max nice priority         0                    0
196Max realtime priority     99                   99
197Max realtime timeout      unlimited            unlimited            us"#,
198        );
199        let limits = super::get_limits_from_reader(reader).unwrap();
200
201        let expected_limits = Limits {
202            max_cpu_time: Default::default(),
203            max_file_size: Default::default(),
204            max_data_size: Default::default(),
205            max_stack_size: Limit {
206                soft: Some(8388608),
207                hard: None,
208            },
209            max_core_file_size: Default::default(),
210            max_resident_set: Default::default(),
211            max_processes: Limit {
212                soft: Some(62935),
213                hard: Some(62935),
214            },
215            max_open_files: Limit {
216                soft: Some(1024),
217                hard: Some(524288),
218            },
219            max_locked_memory: Limit {
220                soft: Some(65536),
221                hard: Some(65536),
222            },
223            max_address_space: Default::default(),
224            max_file_locks: Default::default(),
225            max_pending_signals: Limit {
226                soft: Some(62935),
227                hard: Some(62935),
228            },
229            max_msgqueue_size: Limit {
230                soft: Some(819200),
231                hard: Some(819200),
232            },
233            max_nice_priority: Limit {
234                soft: Some(0),
235                hard: Some(0),
236            },
237            max_realtime_priority: Limit {
238                soft: Some(99),
239                hard: Some(99),
240            },
241            max_realtime_timeout: Default::default(),
242        };
243
244        assert_eq!(limits, expected_limits);
245    }
246}