1use crate::MicrosandboxError;
2use getset::Getters;
3use serde::{Deserialize, Serialize};
4use std::{convert::TryFrom, fmt, str::FromStr};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12#[repr(u32)]
13#[allow(non_camel_case_types)]
14pub enum LinuxRLimitResource {
15 RLIMIT_CPU = 0,
17
18 RLIMIT_FSIZE = 1,
20
21 RLIMIT_DATA = 2,
23
24 RLIMIT_STACK = 3,
26
27 RLIMIT_CORE = 4,
29
30 RLIMIT_RSS = 5,
32
33 RLIMIT_NPROC = 6,
35
36 RLIMIT_NOFILE = 7,
38
39 RLIMIT_MEMLOCK = 8,
41
42 RLIMIT_AS = 9,
44
45 RLIMIT_LOCKS = 10,
47
48 RLIMIT_SIGPENDING = 11,
50
51 RLIMIT_MSGQUEUE = 12,
53
54 RLIMIT_NICE = 13,
56
57 RLIMIT_RTPRIO = 14,
59
60 RLIMIT_RTTIME = 15,
62}
63
64#[derive(Debug, Clone, PartialEq, Eq, Getters)]
90#[getset(get = "pub with_prefix")]
91pub struct LinuxRlimit {
92 resource: LinuxRLimitResource,
94
95 soft: u64,
99
100 hard: u64,
104}
105
106impl LinuxRLimitResource {
111 pub fn as_int(&self) -> u32 {
113 *self as u32
114 }
115}
116
117impl LinuxRlimit {
118 pub fn new(resource: LinuxRLimitResource, soft: u64, hard: u64) -> Self {
137 Self {
138 resource,
139 soft,
140 hard,
141 }
142 }
143}
144
145impl TryFrom<u32> for LinuxRLimitResource {
150 type Error = MicrosandboxError;
151
152 fn try_from(value: u32) -> Result<Self, Self::Error> {
153 match value {
154 0 => Ok(Self::RLIMIT_CPU),
155 1 => Ok(Self::RLIMIT_FSIZE),
156 2 => Ok(Self::RLIMIT_DATA),
157 3 => Ok(Self::RLIMIT_STACK),
158 4 => Ok(Self::RLIMIT_CORE),
159 5 => Ok(Self::RLIMIT_RSS),
160 6 => Ok(Self::RLIMIT_NPROC),
161 7 => Ok(Self::RLIMIT_NOFILE),
162 8 => Ok(Self::RLIMIT_MEMLOCK),
163 9 => Ok(Self::RLIMIT_AS),
164 10 => Ok(Self::RLIMIT_LOCKS),
165 11 => Ok(Self::RLIMIT_SIGPENDING),
166 12 => Ok(Self::RLIMIT_MSGQUEUE),
167 13 => Ok(Self::RLIMIT_NICE),
168 14 => Ok(Self::RLIMIT_RTPRIO),
169 15 => Ok(Self::RLIMIT_RTTIME),
170 _ => Err(MicrosandboxError::InvalidRLimitResource(value.to_string())),
171 }
172 }
173}
174
175impl FromStr for LinuxRLimitResource {
176 type Err = MicrosandboxError;
177
178 fn from_str(s: &str) -> Result<Self, Self::Err> {
179 match s {
180 "RLIMIT_CPU" => Ok(Self::RLIMIT_CPU),
181 "RLIMIT_FSIZE" => Ok(Self::RLIMIT_FSIZE),
182 "RLIMIT_DATA" => Ok(Self::RLIMIT_DATA),
183 "RLIMIT_STACK" => Ok(Self::RLIMIT_STACK),
184 "RLIMIT_CORE" => Ok(Self::RLIMIT_CORE),
185 "RLIMIT_RSS" => Ok(Self::RLIMIT_RSS),
186 "RLIMIT_NPROC" => Ok(Self::RLIMIT_NPROC),
187 "RLIMIT_NOFILE" => Ok(Self::RLIMIT_NOFILE),
188 "RLIMIT_MEMLOCK" => Ok(Self::RLIMIT_MEMLOCK),
189 "RLIMIT_AS" => Ok(Self::RLIMIT_AS),
190 "RLIMIT_LOCKS" => Ok(Self::RLIMIT_LOCKS),
191 "RLIMIT_SIGPENDING" => Ok(Self::RLIMIT_SIGPENDING),
192 "RLIMIT_MSGQUEUE" => Ok(Self::RLIMIT_MSGQUEUE),
193 "RLIMIT_NICE" => Ok(Self::RLIMIT_NICE),
194 "RLIMIT_RTPRIO" => Ok(Self::RLIMIT_RTPRIO),
195 "RLIMIT_RTTIME" => Ok(Self::RLIMIT_RTTIME),
196 _ => Err(MicrosandboxError::InvalidRLimitResource(s.to_string())),
197 }
198 }
199}
200
201impl fmt::Display for LinuxRLimitResource {
202 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203 match self {
204 Self::RLIMIT_CPU => write!(f, "RLIMIT_CPU"),
205 Self::RLIMIT_FSIZE => write!(f, "RLIMIT_FSIZE"),
206 Self::RLIMIT_DATA => write!(f, "RLIMIT_DATA"),
207 Self::RLIMIT_STACK => write!(f, "RLIMIT_STACK"),
208 Self::RLIMIT_CORE => write!(f, "RLIMIT_CORE"),
209 Self::RLIMIT_RSS => write!(f, "RLIMIT_RSS"),
210 Self::RLIMIT_NPROC => write!(f, "RLIMIT_NPROC"),
211 Self::RLIMIT_NOFILE => write!(f, "RLIMIT_NOFILE"),
212 Self::RLIMIT_MEMLOCK => write!(f, "RLIMIT_MEMLOCK"),
213 Self::RLIMIT_AS => write!(f, "RLIMIT_AS"),
214 Self::RLIMIT_LOCKS => write!(f, "RLIMIT_LOCKS"),
215 Self::RLIMIT_SIGPENDING => write!(f, "RLIMIT_SIGPENDING"),
216 Self::RLIMIT_MSGQUEUE => write!(f, "RLIMIT_MSGQUEUE"),
217 Self::RLIMIT_NICE => write!(f, "RLIMIT_NICE"),
218 Self::RLIMIT_RTPRIO => write!(f, "RLIMIT_RTPRIO"),
219 Self::RLIMIT_RTTIME => write!(f, "RLIMIT_RTTIME"),
220 }
221 }
222}
223
224impl FromStr for LinuxRlimit {
225 type Err = MicrosandboxError;
226
227 fn from_str(s: &str) -> Result<Self, Self::Err> {
228 let parts: Vec<&str> = s.split('=').collect();
229 if parts.len() != 2 {
230 return Err(MicrosandboxError::InvalidRLimitFormat(s.to_string()));
231 }
232
233 let resource = if let Ok(resource_num) = parts[0].parse::<u32>() {
234 LinuxRLimitResource::try_from(resource_num)?
235 } else {
236 parts[0].parse()?
237 };
238
239 let limits: Vec<&str> = parts[1].split(':').collect();
240 if limits.len() != 2 {
241 return Err(MicrosandboxError::InvalidRLimitFormat(s.to_string()));
242 }
243
244 let soft = limits[0]
245 .parse()
246 .map_err(|_| MicrosandboxError::InvalidRLimitValue(limits[0].to_string()))?;
247 let hard = limits[1]
248 .parse()
249 .map_err(|_| MicrosandboxError::InvalidRLimitValue(limits[1].to_string()))?;
250
251 Ok(Self::new(resource, soft, hard))
252 }
253}
254
255impl fmt::Display for LinuxRlimit {
256 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257 write!(f, "{}={}:{}", self.resource.as_int(), self.soft, self.hard)
258 }
259}
260
261impl Serialize for LinuxRlimit {
262 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
263 where
264 S: serde::Serializer,
265 {
266 serializer.serialize_str(&self.to_string())
267 }
268}
269
270impl<'de> Deserialize<'de> for LinuxRlimit {
271 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
272 where
273 D: serde::Deserializer<'de>,
274 {
275 let s = String::deserialize(deserializer)?;
276 Self::from_str(&s).map_err(serde::de::Error::custom)
277 }
278}
279
280#[cfg(test)]
285mod tests {
286 use super::*;
287
288 #[test]
289 fn test_linux_rlimit_resource_from_u32() -> anyhow::Result<()> {
290 assert_eq!(
291 LinuxRLimitResource::try_from(0)?,
292 LinuxRLimitResource::RLIMIT_CPU
293 );
294 assert_eq!(
295 LinuxRLimitResource::try_from(7)?,
296 LinuxRLimitResource::RLIMIT_NOFILE
297 );
298 assert_eq!(
299 LinuxRLimitResource::try_from(15)?,
300 LinuxRLimitResource::RLIMIT_RTTIME
301 );
302 assert!(LinuxRLimitResource::try_from(16).is_err());
303 Ok(())
304 }
305
306 #[test]
307 fn test_linux_rlimit_resource_as_int() {
308 assert_eq!(LinuxRLimitResource::RLIMIT_CPU.as_int(), 0);
309 assert_eq!(LinuxRLimitResource::RLIMIT_NOFILE.as_int(), 7);
310 assert_eq!(LinuxRLimitResource::RLIMIT_RTTIME.as_int(), 15);
311 }
312
313 #[test]
314 fn test_linux_rlimit_resource_from_str() -> anyhow::Result<()> {
315 assert_eq!(
316 "RLIMIT_CPU".parse::<LinuxRLimitResource>()?,
317 LinuxRLimitResource::RLIMIT_CPU
318 );
319 assert_eq!(
320 "RLIMIT_NOFILE".parse::<LinuxRLimitResource>()?,
321 LinuxRLimitResource::RLIMIT_NOFILE
322 );
323 assert_eq!(
324 "RLIMIT_RTTIME".parse::<LinuxRLimitResource>()?,
325 LinuxRLimitResource::RLIMIT_RTTIME
326 );
327 assert!("RLIMIT_INVALID".parse::<LinuxRLimitResource>().is_err());
328 Ok(())
329 }
330
331 #[test]
332 fn test_linux_rlimit_resource_display() {
333 assert_eq!(LinuxRLimitResource::RLIMIT_CPU.to_string(), "RLIMIT_CPU");
334 assert_eq!(
335 LinuxRLimitResource::RLIMIT_NOFILE.to_string(),
336 "RLIMIT_NOFILE"
337 );
338 assert_eq!(
339 LinuxRLimitResource::RLIMIT_RTTIME.to_string(),
340 "RLIMIT_RTTIME"
341 );
342 }
343
344 #[test]
345 fn test_linux_rlimit_new() {
346 let rlimit = LinuxRlimit::new(LinuxRLimitResource::RLIMIT_CPU, 10, 20);
347 assert_eq!(rlimit.resource, LinuxRLimitResource::RLIMIT_CPU);
348 assert_eq!(rlimit.soft, 10);
349 assert_eq!(rlimit.hard, 20);
350
351 let rlimit = LinuxRlimit::new(LinuxRLimitResource::RLIMIT_NOFILE, 1000, 2000);
352 assert_eq!(rlimit.resource, LinuxRLimitResource::RLIMIT_NOFILE);
353 assert_eq!(rlimit.soft, 1000);
354 assert_eq!(rlimit.hard, 2000);
355 }
356
357 #[test]
358 fn test_linux_rlimit_from_str_with_rlimit_syntax() -> anyhow::Result<()> {
359 let rlimit: LinuxRlimit = "RLIMIT_CPU=10:20".parse()?;
360 assert_eq!(rlimit.resource, LinuxRLimitResource::RLIMIT_CPU);
361 assert_eq!(rlimit.soft, 10);
362 assert_eq!(rlimit.hard, 20);
363
364 let rlimit: LinuxRlimit = "RLIMIT_NOFILE=1000:2000".parse()?;
365 assert_eq!(rlimit.resource, LinuxRLimitResource::RLIMIT_NOFILE);
366 assert_eq!(rlimit.soft, 1000);
367 assert_eq!(rlimit.hard, 2000);
368
369 let rlimit: LinuxRlimit = "RLIMIT_AS=1048576:2097152".parse()?;
370 assert_eq!(rlimit.resource, LinuxRLimitResource::RLIMIT_AS);
371 assert_eq!(rlimit.soft, 1048576);
372 assert_eq!(rlimit.hard, 2097152);
373
374 assert!("RLIMIT_INVALID=10:20".parse::<LinuxRlimit>().is_err());
375 assert!("RLIMIT_CPU=10".parse::<LinuxRlimit>().is_err());
376 assert!("RLIMIT_CPU=10:".parse::<LinuxRlimit>().is_err());
377 assert!("RLIMIT_CPU=:20".parse::<LinuxRlimit>().is_err());
378 Ok(())
379 }
380
381 #[test]
382 fn test_linux_rlimit_from_str_mixed_syntax() -> anyhow::Result<()> {
383 let rlimit: LinuxRlimit = "0=10:20".parse()?;
384 assert_eq!(rlimit.resource, LinuxRLimitResource::RLIMIT_CPU);
385 assert_eq!(rlimit.soft, 10);
386 assert_eq!(rlimit.hard, 20);
387
388 let rlimit: LinuxRlimit = "RLIMIT_NOFILE=1000:2000".parse()?;
389 assert_eq!(rlimit.resource, LinuxRLimitResource::RLIMIT_NOFILE);
390 assert_eq!(rlimit.soft, 1000);
391 assert_eq!(rlimit.hard, 2000);
392
393 Ok(())
394 }
395
396 #[test]
397 fn test_linux_rlimit_display() {
398 let rlimit = LinuxRlimit::new(LinuxRLimitResource::RLIMIT_CPU, 10, 20);
399 assert_eq!(rlimit.to_string(), "0=10:20");
400
401 let rlimit = LinuxRlimit::new(LinuxRLimitResource::RLIMIT_NOFILE, 1000, 2000);
402 assert_eq!(rlimit.to_string(), "7=1000:2000");
403 }
404
405 #[test]
406 fn test_linux_rlimit_serialize_deserialize() -> anyhow::Result<()> {
407 let rlimit = LinuxRlimit::new(LinuxRLimitResource::RLIMIT_CPU, 10, 20);
408 let serialized = serde_json::to_string(&rlimit)?;
409 assert_eq!(serialized, "\"0=10:20\"");
410
411 let deserialized: LinuxRlimit = serde_json::from_str(&serialized)?;
412 assert_eq!(deserialized, rlimit);
413
414 let rlimit = LinuxRlimit::new(LinuxRLimitResource::RLIMIT_NOFILE, 1000, 2000);
415 let serialized = serde_json::to_string(&rlimit)?;
416 assert_eq!(serialized, "\"7=1000:2000\"");
417
418 let deserialized: LinuxRlimit = serde_json::from_str(&serialized)?;
419 assert_eq!(deserialized, rlimit);
420
421 Ok(())
422 }
423}