1use crate::errors::{Errno, error};
6use crate::{
7 FUTEX_TID_MASK, RLIM_INFINITY, RLIMIT_AS, RLIMIT_CORE, RLIMIT_CPU, RLIMIT_DATA, RLIMIT_FSIZE,
8 RLIMIT_LOCKS, RLIMIT_MEMLOCK, RLIMIT_MSGQUEUE, RLIMIT_NICE, RLIMIT_NOFILE, RLIMIT_NPROC,
9 RLIMIT_RSS, RLIMIT_RTPRIO, RLIMIT_RTTIME, RLIMIT_SIGPENDING, RLIMIT_STACK, rlimit, uapi,
10};
11use std::collections::HashMap;
12
13pub struct ResourceDesc {
15 pub name: &'static str,
17
18 pub unit: &'static str,
20}
21
22impl ResourceDesc {
23 fn new(name: &'static str, unit: &'static str) -> ResourceDesc {
24 ResourceDesc { name, unit }
25 }
26}
27
28#[allow(clippy::upper_case_acronyms)]
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
30pub enum Resource {
31 CPU,
32 FSIZE,
33 DATA,
34 STACK,
35 CORE,
36 RSS,
37 NPROC,
38 NOFILE,
39 MEMLOCK,
40 AS,
41 LOCKS,
42 SIGPENDING,
43 MSGQUEUE,
44 NICE,
45 RTPRIO,
46 RTTIME,
47}
48
49impl Resource {
50 pub const ALL: [Resource; 16] = [
51 Resource::CPU,
52 Resource::FSIZE,
53 Resource::DATA,
54 Resource::STACK,
55 Resource::CORE,
56 Resource::RSS,
57 Resource::NPROC,
58 Resource::NOFILE,
59 Resource::MEMLOCK,
60 Resource::AS,
61 Resource::LOCKS,
62 Resource::SIGPENDING,
63 Resource::MSGQUEUE,
64 Resource::NICE,
65 Resource::RTPRIO,
66 Resource::RTTIME,
67 ];
68
69 pub fn from_raw(raw: u32) -> Result<Resource, Errno> {
70 Ok(match raw {
71 RLIMIT_CPU => Resource::CPU,
72 RLIMIT_FSIZE => Resource::FSIZE,
73 RLIMIT_DATA => Resource::DATA,
74 RLIMIT_STACK => Resource::STACK,
75 RLIMIT_CORE => Resource::CORE,
76 RLIMIT_RSS => Resource::RSS,
77 RLIMIT_NPROC => Resource::NPROC,
78 RLIMIT_NOFILE => Resource::NOFILE,
79 RLIMIT_MEMLOCK => Resource::MEMLOCK,
80 RLIMIT_AS => Resource::AS,
81 RLIMIT_LOCKS => Resource::LOCKS,
82 RLIMIT_SIGPENDING => Resource::SIGPENDING,
83 RLIMIT_MSGQUEUE => Resource::MSGQUEUE,
84 RLIMIT_NICE => Resource::NICE,
85 RLIMIT_RTPRIO => Resource::RTPRIO,
86 RLIMIT_RTTIME => Resource::RTTIME,
87 _ => return error!(EINVAL),
88 })
89 }
90
91 pub fn desc(&self) -> ResourceDesc {
92 match self {
93 Resource::CPU => ResourceDesc::new("Max cpu time", "seconds"),
94 Resource::FSIZE => ResourceDesc::new("Max file size", "bytes"),
95 Resource::DATA => ResourceDesc::new("Max data size", "bytes"),
96 Resource::STACK => ResourceDesc::new("Max stack size", "bytes"),
97 Resource::CORE => ResourceDesc::new("Max core file size", "bytes"),
98 Resource::RSS => ResourceDesc::new("Max resident set", "bytes"),
99 Resource::NPROC => ResourceDesc::new("Max processes", "processes"),
100 Resource::NOFILE => ResourceDesc::new("Max open files", "files"),
101 Resource::MEMLOCK => ResourceDesc::new("Max locked memory", "bytes"),
102 Resource::AS => ResourceDesc::new("Max address space", "bytes"),
103 Resource::LOCKS => ResourceDesc::new("Max file locks", "bytes"),
104 Resource::SIGPENDING => ResourceDesc::new("Max pending signals", "signals"),
105 Resource::MSGQUEUE => ResourceDesc::new("Max msgqueue size", "bytes"),
106 Resource::NICE => ResourceDesc::new("Max nice priority", ""),
107 Resource::RTPRIO => ResourceDesc::new("Max realtime priority", ""),
108 Resource::RTTIME => ResourceDesc::new("Max realtime timeout", "us"),
109 }
110 }
111}
112
113impl std::str::FromStr for Resource {
114 type Err = Errno;
115
116 fn from_str(name: &str) -> Result<Self, Self::Err> {
117 Ok(match name {
118 "CPU" => Resource::CPU,
119 "FSIZE" => Resource::FSIZE,
120 "DATA" => Resource::DATA,
121 "STACK" => Resource::STACK,
122 "CORE" => Resource::CORE,
123 "RSS" => Resource::RSS,
124 "NPROC" => Resource::NPROC,
125 "NOFILE" => Resource::NOFILE,
126 "MEMLOCK" => Resource::MEMLOCK,
127 "AS" => Resource::AS,
128 "LOCKS" => Resource::LOCKS,
129 "SIGPENDING" => Resource::SIGPENDING,
130 "MSGQUEUE" => Resource::MSGQUEUE,
131 "NICE" => Resource::NICE,
132 "RTPRIO" => Resource::RTPRIO,
133 "RTTIME" => Resource::RTTIME,
134 _ => return error!(EINVAL),
135 })
136 }
137}
138
139#[derive(Debug, Clone)]
140pub struct ResourceLimits {
141 values: HashMap<Resource, rlimit>,
142}
143
144const INFINITE_LIMIT: rlimit =
145 rlimit { rlim_cur: RLIM_INFINITY as u64, rlim_max: RLIM_INFINITY as u64 };
146
147const DEFAULT_NOFILE_MAX: u64 = 524288;
149
150pub fn default_threads_max(total_ram_bytes: u64) -> u64 {
163 const MIN_THREADS: u64 = 20;
164 const THREAD_STACK_SIZE: u64 = 16384;
165 (total_ram_bytes / (8 * THREAD_STACK_SIZE)).clamp(MIN_THREADS, FUTEX_TID_MASK as u64)
166}
167
168const DEFAULT_LIMITS: [(Resource, rlimit); 7] = [
169 (Resource::STACK, rlimit { rlim_cur: uapi::_STK_LIM as u64, rlim_max: RLIM_INFINITY as u64 }),
170 (Resource::CORE, rlimit { rlim_cur: 0, rlim_max: RLIM_INFINITY as u64 }),
171 (
172 Resource::NOFILE,
173 rlimit { rlim_cur: uapi::INR_OPEN_CUR as u64, rlim_max: DEFAULT_NOFILE_MAX },
174 ),
175 (
176 Resource::MEMLOCK,
177 rlimit { rlim_cur: uapi::MLOCK_LIMIT as u64, rlim_max: uapi::MLOCK_LIMIT as u64 },
178 ),
179 (
180 Resource::MSGQUEUE,
181 rlimit { rlim_cur: uapi::MQ_BYTES_MAX as u64, rlim_max: uapi::MQ_BYTES_MAX as u64 },
182 ),
183 (Resource::NICE, rlimit { rlim_cur: 0, rlim_max: 0 }),
184 (Resource::RTPRIO, rlimit { rlim_cur: 0, rlim_max: 0 }),
185];
186
187impl ResourceLimits {
190 pub fn from_total_ram(total_ram_bytes: u64) -> Self {
193 let mut limits = Self { values: Default::default() };
194 for (resource, value) in DEFAULT_LIMITS {
195 limits.set(resource, value);
196 }
197 let scaled_limit = default_threads_max(total_ram_bytes) / 2;
198 limits.set(Resource::NPROC, rlimit { rlim_cur: scaled_limit, rlim_max: scaled_limit });
199 limits.set(Resource::SIGPENDING, rlimit { rlim_cur: scaled_limit, rlim_max: scaled_limit });
200 limits
201 }
202
203 pub fn get(&self, resource: Resource) -> rlimit {
204 *self.values.get(&resource).unwrap_or(&INFINITE_LIMIT)
205 }
206
207 pub fn set(&mut self, resource: Resource, value: rlimit) {
208 self.values.insert(resource, value);
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[::fuchsia::test]
217 fn test_resource_from_str() {
218 for resource in Resource::ALL {
219 let name = format!("{resource:?}");
220 assert_eq!(name.parse::<Resource>(), Ok(resource));
221 }
222 assert!("".parse::<Resource>().is_err());
223 assert!("RLIMIT_NOFILE".parse::<Resource>().is_err());
224 assert!("UNKNOWN".parse::<Resource>().is_err());
225 }
226
227 #[::fuchsia::test]
228 fn test_threads_max_and_scaled_limits() {
229 let ram_4gb = 4 * 1024 * 1024 * 1024;
232 assert_eq!(default_threads_max(ram_4gb), 32768);
233
234 let limits = ResourceLimits::from_total_ram(ram_4gb);
235 let nproc = limits.get(Resource::NPROC);
236 assert_eq!(nproc.rlim_cur, 16384);
237 assert_eq!(nproc.rlim_max, 16384);
238
239 let sigpending = limits.get(Resource::SIGPENDING);
240 assert_eq!(sigpending.rlim_cur, 16384);
241 assert_eq!(sigpending.rlim_max, 16384);
242
243 let nofile = limits.get(Resource::NOFILE);
244 assert_eq!(nofile.rlim_cur, 1024);
245 assert_eq!(nofile.rlim_max, 524288);
246
247 let memlock = limits.get(Resource::MEMLOCK);
248 assert_eq!(memlock.rlim_cur, 8 * 1024 * 1024);
249 assert_eq!(memlock.rlim_max, 8 * 1024 * 1024);
250
251 let stack = limits.get(Resource::STACK);
252 assert_eq!(stack.rlim_cur, 8 * 1024 * 1024);
253 assert_eq!(stack.rlim_max, RLIM_INFINITY as u64);
254 }
255
256 #[::fuchsia::test]
257 fn test_threads_max_clamping() {
258 assert_eq!(default_threads_max(0), 20);
259 assert_eq!(default_threads_max(1024), 20);
260 assert_eq!(default_threads_max(u64::MAX), FUTEX_TID_MASK as u64);
261 }
262}