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